{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "4c96485e",
   "metadata": {},
   "outputs": [],
   "source": [
    "import warnings\n",
    "warnings.filterwarnings('ignore')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "fb1c8b6f",
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import gc\n",
    "import numpy as np\n",
    "import pandas as pd\n",
    "import re\n",
    "import time\n",
    "from scipy import stats\n",
    "import matplotlib.pyplot as plt\n",
    "import category_encoders as ce\n",
    "import networkx as nx\n",
    "import pickle\n",
    "import lightgbm as lgb\n",
    "import catboost as cat\n",
    "import xgboost as xgb\n",
    "import seaborn as sns\n",
    "from datetime import timedelta\n",
    "from gensim.models import Word2Vec\n",
    "from io import StringIO\n",
    "from tqdm import tqdm\n",
    "from lightgbm import LGBMClassifier\n",
    "from lightgbm import log_evaluation, early_stopping\n",
    "from sklearn.metrics import roc_curve\n",
    "from scipy.stats import chi2_contingency, pearsonr\n",
    "from sklearn.preprocessing import StandardScaler, OneHotEncoder, LabelEncoder\n",
    "from sklearn.feature_extraction.text import TfidfVectorizer, CountVectorizer\n",
    "from sklearn.feature_extraction import FeatureHasher\n",
    "from sklearn.model_selection import StratifiedKFold, KFold, train_test_split, GridSearchCV\n",
    "from category_encoders import TargetEncoder\n",
    "from sklearn.decomposition import TruncatedSVD\n",
    "from autogluon.tabular import TabularDataset, TabularPredictor, FeatureMetadata\n",
    "from autogluon.features.generators import AsTypeFeatureGenerator, BulkFeatureGenerator, DropUniqueFeatureGenerator, FillNaFeatureGenerator, PipelineFeatureGenerator\n",
    "from autogluon.features.generators import CategoryFeatureGenerator, IdentityFeatureGenerator, AutoMLPipelineFeatureGenerator\n",
    "from autogluon.common.features.types import R_INT, R_FLOAT\n",
    "from autogluon.core.metrics import make_scorer"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7e71fa04",
   "metadata": {},
   "source": [
    "# 数据导入"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4b8030ea",
   "metadata": {},
   "source": [
    "## 数据导入通用函数"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "ea884f9b",
   "metadata": {},
   "outputs": [],
   "source": [
    "def load_data_from_directory(directory):\n",
    "    \"\"\"\n",
    "    遍历目录加载所有CSV文件，将其作为独立的DataFrame变量\n",
    "\n",
    "    参数:\n",
    "    - directory: 输入的数据路径\n",
    "    \n",
    "    返回:\n",
    "    - 含有数据集名称的列表\n",
    "    \"\"\"\n",
    "    dataset_names = []\n",
    "    for filename in os.listdir(directory):\n",
    "        if filename.endswith(\".csv\"):\n",
    "            dataset_name = os.path.splitext(filename)[0] + '_data' # 获取文件名作为变量名\n",
    "            file_path = os.path.join(directory, filename)  # 完整的文件路径\n",
    "            globals()[dataset_name] = pd.read_csv(file_path)  # 将文件加载为DataFrame并赋值给全局变量\n",
    "            dataset_names.append(dataset_name)\n",
    "            print(f\"数据集 {dataset_name} 已加载为 DataFrame\")\n",
    "\n",
    "    return dataset_names"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "e2173f06",
   "metadata": {},
   "source": [
    "## 训练集导入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "63ed3faf",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "数据集 TRAIN_ASSET_DEBT_data 已加载为 DataFrame\n",
      "数据集 TRAIN_CCD_TR_DTL_data 已加载为 DataFrame\n",
      "数据集 TRAIN_MB_CUST_INFO_data 已加载为 DataFrame\n",
      "数据集 TRAIN_MB_PAGEVIEW_DTL_data 已加载为 DataFrame\n",
      "数据集 TRAIN_MB_TRNFLW_DTL_data 已加载为 DataFrame\n",
      "数据集 TRAIN_NATURE_data 已加载为 DataFrame\n",
      "数据集 TRAIN_PROD_HOLD_data 已加载为 DataFrame\n",
      "数据集 TRAIN_TARGET_data 已加载为 DataFrame\n",
      "数据集 TRAIN_TR_APS_DTL_data 已加载为 DataFrame\n"
     ]
    }
   ],
   "source": [
    "train_load_dt = './data/Train'\n",
    "train_data_name = load_data_from_directory(train_load_dt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "61c6829d",
   "metadata": {},
   "source": [
    "## 测试集导入"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "7ea8c6d1",
   "metadata": {},
   "outputs": [],
   "source": [
    "#A_load_dt = './data/A'\n",
    "#A_data_name = load_data_from_directory(A_load_dt)"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3162e129",
   "metadata": {},
   "source": [
    "# 数据探查"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "58b11485",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATA_DAT</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>AGE</th>\n",
       "      <th>IDV_CUST_SEX</th>\n",
       "      <th>IDV_CUST_MRGE_STS</th>\n",
       "      <th>IDV_CUST_HEDU</th>\n",
       "      <th>IDV_CUST_CRT_TIME</th>\n",
       "      <th>IDV_CUST_VLU_RANK</th>\n",
       "      <th>HOLD_DCARD_CNT</th>\n",
       "      <th>HOLD_CCARD_CNT</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20131231</td>\n",
       "      <td>ee39a885c34dc7b3ef1d6078368608c4</td>\n",
       "      <td>38</td>\n",
       "      <td>B</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>19991101.0</td>\n",
       "      <td>B</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20131231</td>\n",
       "      <td>8af0ca419efe285afe630122f1bc668c</td>\n",
       "      <td>43</td>\n",
       "      <td>B</td>\n",
       "      <td>A</td>\n",
       "      <td>63543665934598786c80e3f38ee5fc1c</td>\n",
       "      <td>19960103.0</td>\n",
       "      <td>D</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20131231</td>\n",
       "      <td>4f5882570aa441c90fbecef4a1cba1a3</td>\n",
       "      <td>47</td>\n",
       "      <td>B</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>20051125.0</td>\n",
       "      <td>B</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20131231</td>\n",
       "      <td>11e1109832dfab1ee347acf2f8f12a30</td>\n",
       "      <td>32</td>\n",
       "      <td>B</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>19990525.0</td>\n",
       "      <td>B</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20131231</td>\n",
       "      <td>6efb73cba2d64ac1c4b1cc0541b9f8b8</td>\n",
       "      <td>44</td>\n",
       "      <td>A</td>\n",
       "      <td>A</td>\n",
       "      <td>0a7af49dedd3c53afbbd1b8a7b7e185e</td>\n",
       "      <td>19990225.0</td>\n",
       "      <td>A</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   DATA_DAT                           CUST_NO  AGE IDV_CUST_SEX  \\\n",
       "0  20131231  ee39a885c34dc7b3ef1d6078368608c4   38            B   \n",
       "1  20131231  8af0ca419efe285afe630122f1bc668c   43            B   \n",
       "2  20131231  4f5882570aa441c90fbecef4a1cba1a3   47            B   \n",
       "3  20131231  11e1109832dfab1ee347acf2f8f12a30   32            B   \n",
       "4  20131231  6efb73cba2d64ac1c4b1cc0541b9f8b8   44            A   \n",
       "\n",
       "  IDV_CUST_MRGE_STS                     IDV_CUST_HEDU  IDV_CUST_CRT_TIME  \\\n",
       "0               NaN                               NaN         19991101.0   \n",
       "1                 A  63543665934598786c80e3f38ee5fc1c         19960103.0   \n",
       "2               NaN                               NaN         20051125.0   \n",
       "3               NaN                               NaN         19990525.0   \n",
       "4                 A  0a7af49dedd3c53afbbd1b8a7b7e185e         19990225.0   \n",
       "\n",
       "  IDV_CUST_VLU_RANK  HOLD_DCARD_CNT  HOLD_CCARD_CNT  \n",
       "0                 B               1               0  \n",
       "1                 D               2               0  \n",
       "2                 B               1               0  \n",
       "3                 B               1               0  \n",
       "4                 A               1               0  "
      ]
     },
     "execution_count": 10,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_NATURE_data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "bbfd21ee",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATA_DAT</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>FA_BAL</th>\n",
       "      <th>FA_MAVER_BAL</th>\n",
       "      <th>FA_BAL_LST_3_MTH</th>\n",
       "      <th>FA_BAL_LST_6_MTH</th>\n",
       "      <th>FA_BAL_LST_12_MTH</th>\n",
       "      <th>AUM_BAL</th>\n",
       "      <th>AUM_MAVER_BAL</th>\n",
       "      <th>AUM_BAL_LST_3_MTH</th>\n",
       "      <th>...</th>\n",
       "      <th>LOAN_BAL_LST_6_MTH</th>\n",
       "      <th>FNCG_BAL</th>\n",
       "      <th>FNCG_MAVER_BAL</th>\n",
       "      <th>FNCG_YAVER_BAL</th>\n",
       "      <th>FUND_BAL</th>\n",
       "      <th>FUND_MAVER_BAL</th>\n",
       "      <th>FUND_YAVER_BAL</th>\n",
       "      <th>INSUR_BAL</th>\n",
       "      <th>INSUR_MAVER_BAL</th>\n",
       "      <th>INSUR_YAVER_BAL</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20131231</td>\n",
       "      <td>63b26174d0346c844e40cf2397d313f2</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.51</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>0.00</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>33.56</td>\n",
       "      <td>33.56</td>\n",
       "      <td>33.56</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20131231</td>\n",
       "      <td>a135f3861cdf0b6e5c63852c96cfb74e</td>\n",
       "      <td>1.76</td>\n",
       "      <td>4.91</td>\n",
       "      <td>6.46</td>\n",
       "      <td>11.33</td>\n",
       "      <td>10.94</td>\n",
       "      <td>1.76</td>\n",
       "      <td>4.91</td>\n",
       "      <td>6.46</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>1.12</td>\n",
       "      <td>1.12</td>\n",
       "      <td>1.11</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20131231</td>\n",
       "      <td>5ef963c2df624d4e65e4d0e50de64420</td>\n",
       "      <td>9.67</td>\n",
       "      <td>10.08</td>\n",
       "      <td>10.34</td>\n",
       "      <td>9.95</td>\n",
       "      <td>8.62</td>\n",
       "      <td>9.67</td>\n",
       "      <td>10.08</td>\n",
       "      <td>10.34</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20131231</td>\n",
       "      <td>6fdf44f63582ba96aea40060c54562ba</td>\n",
       "      <td>129.81</td>\n",
       "      <td>129.22</td>\n",
       "      <td>128.36</td>\n",
       "      <td>127.96</td>\n",
       "      <td>126.60</td>\n",
       "      <td>129.81</td>\n",
       "      <td>129.22</td>\n",
       "      <td>128.36</td>\n",
       "      <td>...</td>\n",
       "      <td>NaN</td>\n",
       "      <td>61.91</td>\n",
       "      <td>61.89</td>\n",
       "      <td>61.28</td>\n",
       "      <td>82.65</td>\n",
       "      <td>82.74</td>\n",
       "      <td>80.83</td>\n",
       "      <td>29.32</td>\n",
       "      <td>46.54</td>\n",
       "      <td>45.03</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20131231</td>\n",
       "      <td>6efb73cba2d64ac1c4b1cc0541b9f8b8</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>...</td>\n",
       "      <td>0.0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 34 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "   DATA_DAT                           CUST_NO  FA_BAL  FA_MAVER_BAL  \\\n",
       "0  20131231  63b26174d0346c844e40cf2397d313f2   58.52         58.52   \n",
       "1  20131231  a135f3861cdf0b6e5c63852c96cfb74e    1.76          4.91   \n",
       "2  20131231  5ef963c2df624d4e65e4d0e50de64420    9.67         10.08   \n",
       "3  20131231  6fdf44f63582ba96aea40060c54562ba  129.81        129.22   \n",
       "4  20131231  6efb73cba2d64ac1c4b1cc0541b9f8b8    0.92          0.92   \n",
       "\n",
       "   FA_BAL_LST_3_MTH  FA_BAL_LST_6_MTH  FA_BAL_LST_12_MTH  AUM_BAL  \\\n",
       "0             58.52             58.52              58.51    58.52   \n",
       "1              6.46             11.33              10.94     1.76   \n",
       "2             10.34              9.95               8.62     9.67   \n",
       "3            128.36            127.96             126.60   129.81   \n",
       "4              0.92              0.92               0.92     0.92   \n",
       "\n",
       "   AUM_MAVER_BAL  AUM_BAL_LST_3_MTH  ...  LOAN_BAL_LST_6_MTH  FNCG_BAL  \\\n",
       "0          58.52              58.52  ...                 NaN      0.00   \n",
       "1           4.91               6.46  ...                 NaN      1.12   \n",
       "2          10.08              10.34  ...                 NaN       NaN   \n",
       "3         129.22             128.36  ...                 NaN     61.91   \n",
       "4           0.92               0.92  ...                 0.0       NaN   \n",
       "\n",
       "   FNCG_MAVER_BAL  FNCG_YAVER_BAL  FUND_BAL  FUND_MAVER_BAL  FUND_YAVER_BAL  \\\n",
       "0            0.00            0.00       NaN             NaN             NaN   \n",
       "1            1.12            1.11       NaN             NaN             NaN   \n",
       "2             NaN             NaN       NaN             NaN             NaN   \n",
       "3           61.89           61.28     82.65           82.74           80.83   \n",
       "4             NaN             NaN       NaN             NaN             NaN   \n",
       "\n",
       "   INSUR_BAL  INSUR_MAVER_BAL  INSUR_YAVER_BAL  \n",
       "0      33.56            33.56            33.56  \n",
       "1        NaN              NaN              NaN  \n",
       "2        NaN              NaN              NaN  \n",
       "3      29.32            46.54            45.03  \n",
       "4        NaN              NaN              NaN  \n",
       "\n",
       "[5 rows x 34 columns]"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_ASSET_DEBT_data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "97efb30c",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATA_DAT</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>DP_IND</th>\n",
       "      <th>LOAN_IND</th>\n",
       "      <th>DCARD_IND</th>\n",
       "      <th>CCARD_IND</th>\n",
       "      <th>FNCG_IND</th>\n",
       "      <th>FUND_IND</th>\n",
       "      <th>BOND_IND</th>\n",
       "      <th>INSUR_IND</th>\n",
       "      <th>GOLD_IND</th>\n",
       "      <th>TPAY_DCARD_IND</th>\n",
       "      <th>TPAY_CCARD_IND</th>\n",
       "      <th>TPAY_WX_IND</th>\n",
       "      <th>TPAY_ALI_IND</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20131231</td>\n",
       "      <td>ee39a885c34dc7b3ef1d6078368608c4</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20131231</td>\n",
       "      <td>6f196431815ad6541a894fdfbba9d5d6</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20131231</td>\n",
       "      <td>4f5882570aa441c90fbecef4a1cba1a3</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20131231</td>\n",
       "      <td>efd3e688ffbffda9635a3bf65b04facf</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20131231</td>\n",
       "      <td>6efb73cba2d64ac1c4b1cc0541b9f8b8</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   DATA_DAT                           CUST_NO  DP_IND  LOAN_IND  DCARD_IND  \\\n",
       "0  20131231  ee39a885c34dc7b3ef1d6078368608c4       1         0          1   \n",
       "1  20131231  6f196431815ad6541a894fdfbba9d5d6       1         0          1   \n",
       "2  20131231  4f5882570aa441c90fbecef4a1cba1a3       1         0          1   \n",
       "3  20131231  efd3e688ffbffda9635a3bf65b04facf       1         0          1   \n",
       "4  20131231  6efb73cba2d64ac1c4b1cc0541b9f8b8       1         0          1   \n",
       "\n",
       "   CCARD_IND  FNCG_IND  FUND_IND  BOND_IND  INSUR_IND  GOLD_IND  \\\n",
       "0          0         0         0         0          0         0   \n",
       "1          0         0         0         0          0         0   \n",
       "2          0         0         0         0          0         0   \n",
       "3          0         0         0         0          0         0   \n",
       "4          0         0         0         0          0         0   \n",
       "\n",
       "   TPAY_DCARD_IND  TPAY_CCARD_IND  TPAY_WX_IND  TPAY_ALI_IND  \n",
       "0               1               0            1             0  \n",
       "1               1               0            1             1  \n",
       "2               1               0            1             1  \n",
       "3               1               0            1             0  \n",
       "4               0               0            0             0  "
      ]
     },
     "execution_count": 12,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_PROD_HOLD_data.head()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "cd734d99",
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATA_DATE</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>MB_REG_TIME</th>\n",
       "      <th>MB_CUST_TYPE</th>\n",
       "      <th>MB_LOGIN_CNT_1M</th>\n",
       "      <th>MB_LOGIN_CNT_3M</th>\n",
       "      <th>MB_ACTV_CNT_1M</th>\n",
       "      <th>MB_ACTV_CNT_3M</th>\n",
       "      <th>VIEW_MINUTE_1M</th>\n",
       "      <th>VIEW_MINUTE_3M</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20131231</td>\n",
       "      <td>7fca4434fc640f6e8b0bdeaaab281a0f</td>\n",
       "      <td>20110211.0</td>\n",
       "      <td>ec20feb4fe57b894e461d061d2776cf9</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>4</td>\n",
       "      <td>0.00</td>\n",
       "      <td>7.85</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20131231</td>\n",
       "      <td>0bb5ff7f09659154c15db0022a20f825</td>\n",
       "      <td>20110805.0</td>\n",
       "      <td>ec20feb4fe57b894e461d061d2776cf9</td>\n",
       "      <td>3</td>\n",
       "      <td>15</td>\n",
       "      <td>3</td>\n",
       "      <td>15</td>\n",
       "      <td>4.03</td>\n",
       "      <td>15.39</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20131231</td>\n",
       "      <td>8b264a90ce80c134db01fbd2310c973e</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20131231</td>\n",
       "      <td>0508f69d736ae662288c5337288914f2</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20131231</td>\n",
       "      <td>2d818f29bd0c43b76d5c7443216318b5</td>\n",
       "      <td>20000321.0</td>\n",
       "      <td>ec20feb4fe57b894e461d061d2776cf9</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "   DATA_DATE                           CUST_NO  MB_REG_TIME  \\\n",
       "0   20131231  7fca4434fc640f6e8b0bdeaaab281a0f   20110211.0   \n",
       "1   20131231  0bb5ff7f09659154c15db0022a20f825   20110805.0   \n",
       "2   20131231  8b264a90ce80c134db01fbd2310c973e          NaN   \n",
       "3   20131231  0508f69d736ae662288c5337288914f2          NaN   \n",
       "4   20131231  2d818f29bd0c43b76d5c7443216318b5   20000321.0   \n",
       "\n",
       "                       MB_CUST_TYPE  MB_LOGIN_CNT_1M  MB_LOGIN_CNT_3M  \\\n",
       "0  ec20feb4fe57b894e461d061d2776cf9                0                1   \n",
       "1  ec20feb4fe57b894e461d061d2776cf9                3               15   \n",
       "2                               NaN                0                0   \n",
       "3                               NaN                0                0   \n",
       "4  ec20feb4fe57b894e461d061d2776cf9                0                0   \n",
       "\n",
       "   MB_ACTV_CNT_1M  MB_ACTV_CNT_3M  VIEW_MINUTE_1M  VIEW_MINUTE_3M  \n",
       "0               0               4            0.00            7.85  \n",
       "1               3              15            4.03           15.39  \n",
       "2               0               0             NaN             NaN  \n",
       "3               0               0             NaN             NaN  \n",
       "4               0               0             NaN             NaN  "
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_MB_CUST_INFO_data.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "4a0dee6c",
   "metadata": {},
   "source": [
    "# 特征工程"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "c16439d6",
   "metadata": {},
   "source": [
    "## 数据预处理与质量检查"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "429306a4",
   "metadata": {},
   "outputs": [],
   "source": [
    "def check_data_quality(df, table_name):\n",
    "    \"\"\"\n",
    "    数据质量检查函数\n",
    "    \"\"\"\n",
    "    print(f\"\\n{'='*80}\")\n",
    "    print(f\"数据质量检查: {table_name}\")\n",
    "    print(f\"{'='*80}\")\n",
    "    print(f\"数据维度: {df.shape}\")\n",
    "    print(f\"客户数量: {df['CUST_NO'].nunique()}\")\n",
    "    \n",
    "    print(f\"\\n缺失值统计:\")\n",
    "    missing = df.isnull().sum()\n",
    "    missing_pct = (missing / len(df) * 100).round(2)\n",
    "    missing_df = pd.DataFrame({\n",
    "        '缺失数量': missing[missing > 0],\n",
    "        '缺失比例(%)': missing_pct[missing > 0]\n",
    "    }).sort_values('缺失比例(%)', ascending=False)\n",
    "    if len(missing_df) > 0:\n",
    "        print(missing_df)\n",
    "    else:\n",
    "        print(\"无缺失值\")\n",
    "    \n",
    "    print(f\"\\n数据类型分布:\")\n",
    "    print(df.dtypes.value_counts())\n",
    "    \n",
    "    print(f\"\\n数值型字段统计:\")\n",
    "    numeric_cols = df.select_dtypes(include=[np.number]).columns.tolist()\n",
    "    if 'CUST_NO' in numeric_cols:\n",
    "        numeric_cols.remove('CUST_NO')\n",
    "    if len(numeric_cols) > 0:\n",
    "        print(df[numeric_cols].describe().T)\n",
    "    \n",
    "    return missing_df"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "id": "abc0d6f9",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "================================================================================\n",
      "数据质量检查: 自然属性信息表(TRAIN)\n",
      "================================================================================\n",
      "数据维度: (83391, 10)\n",
      "客户数量: 83391\n",
      "\n",
      "缺失值统计:\n",
      "                    缺失数量  缺失比例(%)\n",
      "IDV_CUST_HEDU      32040    38.42\n",
      "IDV_CUST_MRGE_STS  28289    33.92\n",
      "IDV_CUST_VLU_RANK   1884     2.26\n",
      "IDV_CUST_CRT_TIME    238     0.29\n",
      "IDV_CUST_SEX          45     0.05\n",
      "\n",
      "数据类型分布:\n",
      "object     5\n",
      "int64      4\n",
      "float64    1\n",
      "Name: count, dtype: int64\n",
      "\n",
      "数值型字段统计:\n",
      "                     count          mean           std         min  \\\n",
      "DATA_DAT           83391.0  2.013123e+07      0.000000  20131231.0   \n",
      "AGE                83391.0  4.393713e+01     10.473056        19.0   \n",
      "IDV_CUST_CRT_TIME  83153.0  2.000744e+07  73454.947562  18881231.0   \n",
      "HOLD_DCARD_CNT     83391.0  1.832812e+00      1.360145         0.0   \n",
      "HOLD_CCARD_CNT     83391.0  8.445756e-02      0.421684         0.0   \n",
      "\n",
      "                          25%         50%         75%         max  \n",
      "DATA_DAT           20131231.0  20131231.0  20131231.0  20131231.0  \n",
      "AGE                      36.0        44.0        53.0       126.0  \n",
      "IDV_CUST_CRT_TIME  19960812.0  20000425.0  20050331.0  20131231.0  \n",
      "HOLD_DCARD_CNT            1.0         2.0         2.0        22.0  \n",
      "HOLD_CCARD_CNT            0.0         0.0         0.0        17.0  \n",
      "\n",
      "================================================================================\n",
      "数据质量检查: 资产负债表(TRAIN)\n",
      "================================================================================\n",
      "数据维度: (83391, 34)\n",
      "客户数量: 83391\n",
      "\n",
      "缺失值统计:\n",
      "                      缺失数量  缺失比例(%)\n",
      "FUND_YAVER_BAL       77009    92.35\n",
      "FUND_MAVER_BAL       77009    92.35\n",
      "FUND_BAL             77009    92.35\n",
      "FNCG_YAVER_BAL       76242    91.43\n",
      "FNCG_MAVER_BAL       76242    91.43\n",
      "FNCG_BAL             76242    91.43\n",
      "INSUR_YAVER_BAL      75737    90.82\n",
      "INSUR_MAVER_BAL      75737    90.82\n",
      "INSUR_BAL            75737    90.82\n",
      "LOAN_BAL_LST_6_MTH   64335    77.15\n",
      "LOAN_BAL_LST_3_MTH   64335    77.15\n",
      "LOAN_MAVER_BAL       64335    77.15\n",
      "LOAN_BAL             64335    77.15\n",
      "DPSA_BAL_LST_6_MTH    7012     8.41\n",
      "DPSA_BAL_LST_12_MTH   7012     8.41\n",
      "DPSA_BAL_LST_3_MTH    7012     8.41\n",
      "DPSA_BAL              7012     8.41\n",
      "DP_BAL_LST_12_MTH     7012     8.41\n",
      "DP_BAL_LST_6_MTH      7012     8.41\n",
      "DP_BAL_LST_3_MTH      7012     8.41\n",
      "DP_BAL                7012     8.41\n",
      "FA_MAVER_BAL          6848     8.21\n",
      "AUM_BAL_MAX           6848     8.21\n",
      "AUM_BAL_LST_12_MTH    6848     8.21\n",
      "AUM_BAL_LST_6_MTH     6848     8.21\n",
      "AUM_BAL_LST_3_MTH     6848     8.21\n",
      "AUM_MAVER_BAL         6848     8.21\n",
      "AUM_BAL               6848     8.21\n",
      "FA_BAL_LST_12_MTH     6848     8.21\n",
      "FA_BAL_LST_6_MTH      6848     8.21\n",
      "FA_BAL_LST_3_MTH      6848     8.21\n",
      "FA_BAL                6848     8.21\n",
      "\n",
      "数据类型分布:\n",
      "float64    32\n",
      "int64       1\n",
      "object      1\n",
      "Name: count, dtype: int64\n",
      "\n",
      "数值型字段统计:\n",
      "                       count          mean        std         min  \\\n",
      "DATA_DAT             83391.0  2.013123e+07   0.000000  20131231.0   \n",
      "FA_BAL               76543.0  2.955709e+01  31.247555         0.0   \n",
      "FA_MAVER_BAL         76543.0  3.033169e+01  30.456119         0.0   \n",
      "FA_BAL_LST_3_MTH     76543.0  3.051695e+01  29.884593         0.0   \n",
      "FA_BAL_LST_6_MTH     76543.0  3.048520e+01  29.357754         0.0   \n",
      "FA_BAL_LST_12_MTH    76543.0  3.047920e+01  28.646364         0.0   \n",
      "AUM_BAL              76543.0  2.449441e+01  29.386881         0.0   \n",
      "AUM_MAVER_BAL        76543.0  2.531388e+01  28.663429         0.0   \n",
      "AUM_BAL_LST_3_MTH    76543.0  2.533751e+01  28.112704         0.0   \n",
      "AUM_BAL_LST_6_MTH    76543.0  2.525449e+01  27.555109         0.0   \n",
      "AUM_BAL_LST_12_MTH   76543.0  2.551835e+01  26.822571         0.0   \n",
      "AUM_BAL_MAX          76543.0  2.449441e+01  29.386881         0.0   \n",
      "DP_BAL               76379.0  2.249709e+01  26.623709         0.0   \n",
      "DP_BAL_LST_3_MTH     76379.0  2.341000e+01  25.255798         0.0   \n",
      "DP_BAL_LST_6_MTH     76379.0  2.338646e+01  24.687536         0.0   \n",
      "DP_BAL_LST_12_MTH    76379.0  2.376643e+01  24.042480         0.0   \n",
      "DPSA_BAL             76379.0  1.716368e+01  18.595133         0.0   \n",
      "DPSA_BAL_LST_3_MTH   76379.0  1.824399e+01  16.982499         0.0   \n",
      "DPSA_BAL_LST_6_MTH   76379.0  1.830971e+01  16.518075         0.0   \n",
      "DPSA_BAL_LST_12_MTH  76379.0  1.886828e+01  16.145038         0.0   \n",
      "LOAN_BAL             19056.0  3.886556e+01  36.436766         0.0   \n",
      "LOAN_MAVER_BAL       19056.0  4.123837e+01  34.193859         0.0   \n",
      "LOAN_BAL_LST_3_MTH   19056.0  4.261704e+01  33.115136         0.0   \n",
      "LOAN_BAL_LST_6_MTH   19056.0  4.329477e+01  32.483274         0.0   \n",
      "FNCG_BAL              7149.0  1.798453e+01  34.715885         0.0   \n",
      "FNCG_MAVER_BAL        7149.0  1.906130e+01  34.879637         0.0   \n",
      "FNCG_YAVER_BAL        7149.0  2.088240e+01  33.468369         0.0   \n",
      "FUND_BAL              6382.0  8.064602e+00  19.598387         0.0   \n",
      "FUND_MAVER_BAL        6382.0  8.033823e+00  19.304805         0.0   \n",
      "FUND_YAVER_BAL        6382.0  8.786558e+00  19.063954         0.0   \n",
      "INSUR_BAL             7654.0  1.893785e+01  26.776137         0.0   \n",
      "INSUR_MAVER_BAL       7654.0  2.286374e+01  31.041994         0.0   \n",
      "INSUR_YAVER_BAL       7654.0  2.290333e+01  30.320435         0.0   \n",
      "\n",
      "                              25%           50%           75%          max  \n",
      "DATA_DAT             2.013123e+07  2.013123e+07  2.013123e+07  20131231.00  \n",
      "FA_BAL               6.465000e+00  1.939000e+01  4.562000e+01       730.94  \n",
      "FA_MAVER_BAL         8.410000e+00  2.129000e+01  4.555000e+01       731.02  \n",
      "FA_BAL_LST_3_MTH     9.210000e+00  2.195000e+01  4.513000e+01       690.67  \n",
      "FA_BAL_LST_6_MTH     9.680000e+00  2.207000e+01  4.467500e+01       642.62  \n",
      "FA_BAL_LST_12_MTH    1.025000e+01  2.246000e+01  4.428000e+01       590.78  \n",
      "AUM_BAL              5.540000e+00  1.429000e+01  3.411000e+01       730.94  \n",
      "AUM_MAVER_BAL        7.440000e+00  1.614000e+01  3.421500e+01       731.02  \n",
      "AUM_BAL_LST_3_MTH    8.120000e+00  1.659000e+01  3.361000e+01       690.67  \n",
      "AUM_BAL_LST_6_MTH    8.570000e+00  1.677000e+01  3.306000e+01       642.62  \n",
      "AUM_BAL_LST_12_MTH   9.260000e+00  1.753000e+01  3.296000e+01       590.78  \n",
      "AUM_BAL_MAX          5.540000e+00  1.429000e+01  3.411000e+01       730.94  \n",
      "DP_BAL               5.160000e+00  1.340000e+01  3.104000e+01       730.94  \n",
      "DP_BAL_LST_3_MTH     7.800000e+00  1.572000e+01  3.093000e+01       690.67  \n",
      "DP_BAL_LST_6_MTH     8.260000e+00  1.602000e+01  3.044000e+01       642.62  \n",
      "DP_BAL_LST_12_MTH    9.000000e+00  1.683000e+01  3.070000e+01       590.78  \n",
      "DPSA_BAL             4.600000e+00  1.120000e+01  2.359000e+01       389.98  \n",
      "DPSA_BAL_LST_3_MTH   6.950000e+00  1.381000e+01  2.455000e+01       473.28  \n",
      "DPSA_BAL_LST_6_MTH   7.425000e+00  1.424000e+01  2.444000e+01       515.14  \n",
      "DPSA_BAL_LST_12_MTH  8.210000e+00  1.518000e+01  2.517000e+01       453.16  \n",
      "LOAN_BAL             0.000000e+00  4.654000e+01  6.520000e+01       233.84  \n",
      "LOAN_MAVER_BAL       0.000000e+00  4.723000e+01  6.350000e+01       232.68  \n",
      "LOAN_BAL_LST_3_MTH   0.000000e+00  4.821500e+01  6.327000e+01       232.68  \n",
      "LOAN_BAL_LST_6_MTH   0.000000e+00  4.840000e+01  6.325250e+01       232.68  \n",
      "FNCG_BAL             0.000000e+00  1.080000e+00  2.367000e+01       319.99  \n",
      "FNCG_MAVER_BAL       0.000000e+00  1.080000e+00  2.677000e+01       351.83  \n",
      "FNCG_YAVER_BAL       0.000000e+00  1.110000e+00  3.243000e+01       257.04  \n",
      "FUND_BAL             0.000000e+00  2.300000e-01  4.497500e+00       279.42  \n",
      "FUND_MAVER_BAL       0.000000e+00  2.300000e-01  3.500000e+00       278.44  \n",
      "FUND_YAVER_BAL       0.000000e+00  8.300000e-01  6.922500e+00       273.43  \n",
      "INSUR_BAL            0.000000e+00  8.280000e+00  2.932000e+01       309.39  \n",
      "INSUR_MAVER_BAL      0.000000e+00  8.570000e+00  3.694000e+01       350.15  \n",
      "INSUR_YAVER_BAL      0.000000e+00  9.110000e+00  3.691750e+01       318.63  \n",
      "\n",
      "================================================================================\n",
      "数据质量检查: 产品持有信息表(TRAIN)\n",
      "================================================================================\n",
      "数据维度: (83391, 15)\n",
      "客户数量: 83391\n",
      "\n",
      "缺失值统计:\n",
      "无缺失值\n",
      "\n",
      "数据类型分布:\n",
      "int64     14\n",
      "object     1\n",
      "Name: count, dtype: int64\n",
      "\n",
      "数值型字段统计:\n",
      "                  count          mean       std         min         25%  \\\n",
      "DATA_DAT        83391.0  2.013123e+07  0.000000  20131231.0  20131231.0   \n",
      "DP_IND          83391.0  9.013443e-01  0.298201         0.0         1.0   \n",
      "LOAN_IND        83391.0  1.368733e-01  0.343716         0.0         0.0   \n",
      "DCARD_IND       83391.0  9.509180e-01  0.216041         0.0         1.0   \n",
      "CCARD_IND       83391.0  1.034644e-01  0.304566         0.0         0.0   \n",
      "FNCG_IND        83391.0  4.427336e-02  0.205703         0.0         0.0   \n",
      "FUND_IND        83391.0  4.047199e-02  0.197065         0.0         0.0   \n",
      "BOND_IND        83391.0  2.782075e-03  0.052672         0.0         0.0   \n",
      "INSUR_IND       83391.0  5.432241e-02  0.226654         0.0         0.0   \n",
      "GOLD_IND        83391.0  7.434855e-03  0.085905         0.0         0.0   \n",
      "TPAY_DCARD_IND  83391.0  8.038278e-01  0.397103         0.0         1.0   \n",
      "TPAY_CCARD_IND  83391.0  1.008022e-01  0.301068         0.0         0.0   \n",
      "TPAY_WX_IND     83391.0  7.361586e-01  0.440717         0.0         0.0   \n",
      "TPAY_ALI_IND    83391.0  5.717284e-01  0.494831         0.0         0.0   \n",
      "\n",
      "                       50%         75%         max  \n",
      "DATA_DAT        20131231.0  20131231.0  20131231.0  \n",
      "DP_IND                 1.0         1.0         1.0  \n",
      "LOAN_IND               0.0         0.0         1.0  \n",
      "DCARD_IND              1.0         1.0         1.0  \n",
      "CCARD_IND              0.0         0.0         1.0  \n",
      "FNCG_IND               0.0         0.0         1.0  \n",
      "FUND_IND               0.0         0.0         1.0  \n",
      "BOND_IND               0.0         0.0         1.0  \n",
      "INSUR_IND              0.0         0.0         1.0  \n",
      "GOLD_IND               0.0         0.0         1.0  \n",
      "TPAY_DCARD_IND         1.0         1.0         1.0  \n",
      "TPAY_CCARD_IND         0.0         0.0         1.0  \n",
      "TPAY_WX_IND            1.0         1.0         1.0  \n",
      "TPAY_ALI_IND           1.0         1.0         1.0  \n",
      "\n",
      "================================================================================\n",
      "数据质量检查: 掌银客户信息表(TRAIN)\n",
      "================================================================================\n",
      "数据维度: (83391, 10)\n",
      "客户数量: 83391\n",
      "\n",
      "缺失值统计:\n",
      "                 缺失数量  缺失比例(%)\n",
      "VIEW_MINUTE_1M  23912    28.67\n",
      "VIEW_MINUTE_3M  23912    28.67\n",
      "MB_REG_TIME      7333     8.79\n",
      "MB_CUST_TYPE     7333     8.79\n",
      "\n",
      "数据类型分布:\n",
      "int64      5\n",
      "float64    3\n",
      "object     2\n",
      "Name: count, dtype: int64\n",
      "\n",
      "数值型字段统计:\n",
      "                   count          mean           std         min          25%  \\\n",
      "DATA_DATE        83391.0  2.013123e+07      0.000000  20131231.0  20131231.00   \n",
      "MB_REG_TIME      76058.0  2.009206e+07  32513.874919  19970415.0  20070910.00   \n",
      "MB_LOGIN_CNT_1M  83391.0  3.395282e+00      5.392112         0.0         0.00   \n",
      "MB_LOGIN_CNT_3M  83391.0  8.702234e+00     14.517898         0.0         0.00   \n",
      "MB_ACTV_CNT_1M   83391.0  4.325167e+00      6.258077         0.0         0.00   \n",
      "MB_ACTV_CNT_3M   83391.0  1.110417e+01     16.931852         0.0         0.00   \n",
      "VIEW_MINUTE_1M   59479.0  1.844543e+01     38.972238         0.0         2.21   \n",
      "VIEW_MINUTE_3M   59479.0  4.289327e+01     92.683462         0.0         7.63   \n",
      "\n",
      "                         50%           75%          max  \n",
      "DATA_DATE        20131231.00  2.013123e+07  20131231.00  \n",
      "MB_REG_TIME      20100306.00  2.012032e+07  20131231.00  \n",
      "MB_LOGIN_CNT_1M         1.00  4.000000e+00        31.00  \n",
      "MB_LOGIN_CNT_3M         2.00  1.100000e+01        92.00  \n",
      "MB_ACTV_CNT_1M          2.00  6.000000e+00        31.00  \n",
      "MB_ACTV_CNT_3M          4.00  1.400000e+01        92.00  \n",
      "VIEW_MINUTE_1M          8.14  2.108000e+01      3269.99  \n",
      "VIEW_MINUTE_3M         19.64  4.533500e+01      6411.96  \n",
      "                 缺失数量  缺失比例(%)\n",
      "VIEW_MINUTE_1M  23912    28.67\n",
      "VIEW_MINUTE_3M  23912    28.67\n",
      "MB_REG_TIME      7333     8.79\n",
      "MB_CUST_TYPE     7333     8.79\n",
      "\n",
      "数据类型分布:\n",
      "int64      5\n",
      "float64    3\n",
      "object     2\n",
      "Name: count, dtype: int64\n",
      "\n",
      "数值型字段统计:\n",
      "                   count          mean           std         min          25%  \\\n",
      "DATA_DATE        83391.0  2.013123e+07      0.000000  20131231.0  20131231.00   \n",
      "MB_REG_TIME      76058.0  2.009206e+07  32513.874919  19970415.0  20070910.00   \n",
      "MB_LOGIN_CNT_1M  83391.0  3.395282e+00      5.392112         0.0         0.00   \n",
      "MB_LOGIN_CNT_3M  83391.0  8.702234e+00     14.517898         0.0         0.00   \n",
      "MB_ACTV_CNT_1M   83391.0  4.325167e+00      6.258077         0.0         0.00   \n",
      "MB_ACTV_CNT_3M   83391.0  1.110417e+01     16.931852         0.0         0.00   \n",
      "VIEW_MINUTE_1M   59479.0  1.844543e+01     38.972238         0.0         2.21   \n",
      "VIEW_MINUTE_3M   59479.0  4.289327e+01     92.683462         0.0         7.63   \n",
      "\n",
      "                         50%           75%          max  \n",
      "DATA_DATE        20131231.00  2.013123e+07  20131231.00  \n",
      "MB_REG_TIME      20100306.00  2.012032e+07  20131231.00  \n",
      "MB_LOGIN_CNT_1M         1.00  4.000000e+00        31.00  \n",
      "MB_LOGIN_CNT_3M         2.00  1.100000e+01        92.00  \n",
      "MB_ACTV_CNT_1M          2.00  6.000000e+00        31.00  \n",
      "MB_ACTV_CNT_3M          4.00  1.400000e+01        92.00  \n",
      "VIEW_MINUTE_1M          8.14  2.108000e+01      3269.99  \n",
      "VIEW_MINUTE_3M         19.64  4.533500e+01      6411.96  \n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>缺失数量</th>\n",
       "      <th>缺失比例(%)</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>VIEW_MINUTE_1M</th>\n",
       "      <td>23912</td>\n",
       "      <td>28.67</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>VIEW_MINUTE_3M</th>\n",
       "      <td>23912</td>\n",
       "      <td>28.67</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>MB_REG_TIME</th>\n",
       "      <td>7333</td>\n",
       "      <td>8.79</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>MB_CUST_TYPE</th>\n",
       "      <td>7333</td>\n",
       "      <td>8.79</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                 缺失数量  缺失比例(%)\n",
       "VIEW_MINUTE_1M  23912    28.67\n",
       "VIEW_MINUTE_3M  23912    28.67\n",
       "MB_REG_TIME      7333     8.79\n",
       "MB_CUST_TYPE     7333     8.79"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "check_data_quality(TRAIN_NATURE_data, '自然属性信息表(TRAIN)')\n",
    "check_data_quality(TRAIN_ASSET_DEBT_data, '资产负债表(TRAIN)')\n",
    "check_data_quality(TRAIN_PROD_HOLD_data, '产品持有信息表(TRAIN)')\n",
    "check_data_quality(TRAIN_MB_CUST_INFO_data, '掌银客户信息表(TRAIN)')"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "d58dd659",
   "metadata": {},
   "source": [
    "## 1. 自然属性信息表特征工程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 20,
   "id": "fe167e29",
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_nature_features(df, is_train=True):\n",
    "    \"\"\"\n",
    "    自然属性信息表特征工程\n",
    "    字段包含: 年龄、性别、婚姻状态、学历、客户建立时间、客户价值等级、持卡数量\n",
    "    \"\"\"\n",
    "    features = df.copy()\n",
    "    prefix = 'NATURE'\n",
    "    \n",
    "    print(f\"\\n{'='*80}\")\n",
    "    print(f\"处理自然属性信息表 - {'训练集' if is_train else '测试集'}\")\n",
    "    print(f\"原始数据维度: {features.shape}\")\n",
    "    print(f\"{'='*80}\\n\")\n",
    "    \n",
    "    reference_date = pd.to_datetime('20131231')\n",
    "    \n",
    "    if 'DATA_DAT' in features.columns:\n",
    "        features = features.drop('DATA_DAT', axis=1)\n",
    "    \n",
    "    print(\"1. 年龄特征处理\")\n",
    "    features[f'{prefix}_AGE'] = features['AGE'].fillna(features['AGE'].median()).astype(int)\n",
    "    features[f'{prefix}_AGE_SQUARE'] = features[f'{prefix}_AGE'] ** 2\n",
    "    features[f'{prefix}_AGE_SQRT'] = np.sqrt(features[f'{prefix}_AGE'])\n",
    "    features[f'{prefix}_AGE_LOG'] = np.log1p(features[f'{prefix}_AGE'])\n",
    "    \n",
    "    features[f'{prefix}_AGE_GROUP_5'] = pd.cut(features[f'{prefix}_AGE'], \n",
    "                                                bins=[0, 25, 35, 45, 55, 100], \n",
    "                                                labels=False) + 1\n",
    "    features[f'{prefix}_AGE_GROUP_5'] = features[f'{prefix}_AGE_GROUP_5'].fillna(0).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_AGE_GROUP_3'] = pd.cut(features[f'{prefix}_AGE'], \n",
    "                                                bins=[0, 30, 50, 100], \n",
    "                                                labels=False) + 1\n",
    "    features[f'{prefix}_AGE_GROUP_3'] = features[f'{prefix}_AGE_GROUP_3'].fillna(0).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_IS_YOUNG'] = (features[f'{prefix}_AGE'] <= 30).astype(int)\n",
    "    features[f'{prefix}_IS_MIDDLE'] = ((features[f'{prefix}_AGE'] > 30) & (features[f'{prefix}_AGE'] <= 50)).astype(int)\n",
    "    features[f'{prefix}_IS_OLD'] = (features[f'{prefix}_AGE'] > 50).astype(int)\n",
    "    features[f'{prefix}_IS_PRIME_AGE'] = ((features[f'{prefix}_AGE'] >= 25) & (features[f'{prefix}_AGE'] <= 45)).astype(int)\n",
    "    \n",
    "    print(\"2. 性别特征处理\")\n",
    "    sex_mapping = {'A': 1, 'B': 2}\n",
    "    features[f'{prefix}_SEX'] = features['IDV_CUST_SEX'].map(sex_mapping).fillna(0).astype(int)\n",
    "    features[f'{prefix}_SEX_MISSING'] = (features[f'{prefix}_SEX'] == 0).astype(int)\n",
    "    features[f'{prefix}_IS_MALE'] = (features[f'{prefix}_SEX'] == 1).astype(int)\n",
    "    features[f'{prefix}_IS_FEMALE'] = (features[f'{prefix}_SEX'] == 2).astype(int)\n",
    "    \n",
    "    print(\"3. 婚姻状况特征处理\")\n",
    "    features[f'{prefix}_MARRIAGE'] = features['IDV_CUST_MRGE_STS'].fillna('UNKNOWN')\n",
    "    features[f'{prefix}_MARRIAGE_MISSING'] = (features[f'{prefix}_MARRIAGE'] == 'UNKNOWN').astype(int)\n",
    "    features[f'{prefix}_IS_SINGLE'] = (features[f'{prefix}_MARRIAGE'] == 'A').astype(int)\n",
    "    features[f'{prefix}_IS_MARRIED'] = (features[f'{prefix}_MARRIAGE'] == 'B').astype(int)\n",
    "    features[f'{prefix}_IS_DIVORCED'] = (features[f'{prefix}_MARRIAGE'] == 'C').astype(int)\n",
    "    \n",
    "    print(\"4. 学历特征处理\")\n",
    "    features[f'{prefix}_EDU'] = features['IDV_CUST_HEDU'].fillna('UNKNOWN')\n",
    "    features[f'{prefix}_EDU_MISSING'] = (features[f'{prefix}_EDU'] == 'UNKNOWN').astype(int)\n",
    "    features[f'{prefix}_EDU_HIGH'] = features[f'{prefix}_EDU'].isin(['D', 'E', 'F']).astype(int)\n",
    "    features[f'{prefix}_EDU_MID'] = features[f'{prefix}_EDU'].isin(['B', 'C']).astype(int)\n",
    "    features[f'{prefix}_EDU_LOW'] = (features[f'{prefix}_EDU'] == 'A').astype(int)\n",
    "    \n",
    "    print(\"5. 客户建立时间特征处理\")\n",
    "    features['IDV_CUST_CRT_TIME_INT'] = features['IDV_CUST_CRT_TIME'].fillna(0).astype(int)\n",
    "    features[f'{prefix}_CRT_DATE'] = pd.to_datetime(features['IDV_CUST_CRT_TIME_INT'].astype(str), format='%Y%m%d', errors='coerce')\n",
    "    \n",
    "    features[f'{prefix}_CRT_DAYS'] = (reference_date - features[f'{prefix}_CRT_DATE']).dt.days\n",
    "    features[f'{prefix}_CRT_DAYS'] = features[f'{prefix}_CRT_DAYS'].fillna(0).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_CRT_YEARS'] = (features[f'{prefix}_CRT_DAYS'] / 365.25).astype(int)\n",
    "    features[f'{prefix}_CRT_MONTHS'] = (features[f'{prefix}_CRT_DAYS'] / 30.44).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_CRT_YEAR'] = features[f'{prefix}_CRT_DATE'].dt.year.fillna(0).astype(int)\n",
    "    features[f'{prefix}_CRT_MONTH'] = features[f'{prefix}_CRT_DATE'].dt.month.fillna(0).astype(int)\n",
    "    features[f'{prefix}_CRT_QUARTER'] = features[f'{prefix}_CRT_DATE'].dt.quarter.fillna(0).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_CRT_TIME_MISSING'] = (features[f'{prefix}_CRT_DAYS'] == 0).astype(int)\n",
    "    features[f'{prefix}_CRT_TIME_VERY_LONG'] = (features[f'{prefix}_CRT_YEARS'] >= 15).astype(int)\n",
    "    features[f'{prefix}_CRT_TIME_LONG'] = ((features[f'{prefix}_CRT_YEARS'] >= 10) & (features[f'{prefix}_CRT_YEARS'] < 15)).astype(int)\n",
    "    features[f'{prefix}_CRT_TIME_MID'] = ((features[f'{prefix}_CRT_YEARS'] >= 5) & (features[f'{prefix}_CRT_YEARS'] < 10)).astype(int)\n",
    "    features[f'{prefix}_CRT_TIME_SHORT'] = ((features[f'{prefix}_CRT_YEARS'] >= 1) & (features[f'{prefix}_CRT_YEARS'] < 5)).astype(int)\n",
    "    features[f'{prefix}_CRT_TIME_VERY_SHORT'] = ((features[f'{prefix}_CRT_YEARS'] > 0) & (features[f'{prefix}_CRT_YEARS'] < 1)).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_IS_OLD_CUSTOMER'] = (features[f'{prefix}_CRT_YEARS'] >= 10).astype(int)\n",
    "    features[f'{prefix}_IS_NEW_CUSTOMER'] = (features[f'{prefix}_CRT_YEARS'] <= 2).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_CRT_DAYS_LOG'] = np.log1p(features[f'{prefix}_CRT_DAYS'])\n",
    "    features[f'{prefix}_CRT_YEARS_LOG'] = np.log1p(features[f'{prefix}_CRT_YEARS'])\n",
    "    \n",
    "    print(\"6. 客户价值等级特征处理\")\n",
    "    vlu_rank_mapping = {'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6}\n",
    "    features[f'{prefix}_VLU_RANK'] = features['IDV_CUST_VLU_RANK'].map(vlu_rank_mapping).fillna(0).astype(int)\n",
    "    features[f'{prefix}_VLU_RANK_MISSING'] = (features[f'{prefix}_VLU_RANK'] == 0).astype(int)\n",
    "    features[f'{prefix}_VLU_RANK_HIGH'] = (features[f'{prefix}_VLU_RANK'] <= 2).astype(int)\n",
    "    features[f'{prefix}_VLU_RANK_MID'] = ((features[f'{prefix}_VLU_RANK'] >= 3) & (features[f'{prefix}_VLU_RANK'] <= 4)).astype(int)\n",
    "    features[f'{prefix}_VLU_RANK_LOW'] = (features[f'{prefix}_VLU_RANK'] >= 5).astype(int)\n",
    "    \n",
    "    print(\"7. 持卡数量特征处理\")\n",
    "    features[f'{prefix}_DCARD_CNT'] = features['HOLD_DCARD_CNT'].fillna(0).astype(int)\n",
    "    features[f'{prefix}_CCARD_CNT'] = features['HOLD_CCARD_CNT'].fillna(0).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_TOTAL_CARD_CNT'] = features[f'{prefix}_DCARD_CNT'] + features[f'{prefix}_CCARD_CNT']\n",
    "    features[f'{prefix}_CARD_DIFF'] = features[f'{prefix}_DCARD_CNT'] - features[f'{prefix}_CCARD_CNT']\n",
    "    features[f'{prefix}_CARD_RATIO'] = features[f'{prefix}_DCARD_CNT'] / (features[f'{prefix}_CCARD_CNT'] + 1)\n",
    "    \n",
    "    features[f'{prefix}_HAS_DCARD'] = (features[f'{prefix}_DCARD_CNT'] > 0).astype(int)\n",
    "    features[f'{prefix}_HAS_CCARD'] = (features[f'{prefix}_CCARD_CNT'] > 0).astype(int)\n",
    "    features[f'{prefix}_NO_CARD'] = ((features[f'{prefix}_DCARD_CNT'] == 0) & (features[f'{prefix}_CCARD_CNT'] == 0)).astype(int)\n",
    "    features[f'{prefix}_ONLY_DCARD'] = ((features[f'{prefix}_DCARD_CNT'] > 0) & (features[f'{prefix}_CCARD_CNT'] == 0)).astype(int)\n",
    "    features[f'{prefix}_ONLY_CCARD'] = ((features[f'{prefix}_DCARD_CNT'] == 0) & (features[f'{prefix}_CCARD_CNT'] > 0)).astype(int)\n",
    "    features[f'{prefix}_HAS_BOTH_CARD'] = ((features[f'{prefix}_DCARD_CNT'] > 0) & (features[f'{prefix}_CCARD_CNT'] > 0)).astype(int)\n",
    "    features[f'{prefix}_MULTI_DCARD'] = (features[f'{prefix}_DCARD_CNT'] > 1).astype(int)\n",
    "    features[f'{prefix}_MULTI_CCARD'] = (features[f'{prefix}_CCARD_CNT'] > 1).astype(int)\n",
    "    \n",
    "    print(\"8. 年龄与建立时间交叉特征\")\n",
    "    features[f'{prefix}_AGE_MINUS_CRT_YEARS'] = features[f'{prefix}_AGE'] - features[f'{prefix}_CRT_YEARS']\n",
    "    features[f'{prefix}_OPEN_AGE'] = features[f'{prefix}_AGE_MINUS_CRT_YEARS']\n",
    "    features[f'{prefix}_OPEN_AGE_YOUNG'] = (features[f'{prefix}_OPEN_AGE'] <= 25).astype(int)\n",
    "    features[f'{prefix}_OPEN_AGE_MID'] = ((features[f'{prefix}_OPEN_AGE'] > 25) & (features[f'{prefix}_OPEN_AGE'] <= 40)).astype(int)\n",
    "    features[f'{prefix}_OPEN_AGE_OLD'] = (features[f'{prefix}_OPEN_AGE'] > 40).astype(int)\n",
    "    \n",
    "    print(\"9. 组合交叉特征\")\n",
    "    features[f'{prefix}_AGE_SEX'] = features[f'{prefix}_AGE'] * features[f'{prefix}_SEX']\n",
    "    features[f'{prefix}_AGE_VLU_RANK'] = features[f'{prefix}_AGE'] * features[f'{prefix}_VLU_RANK']\n",
    "    features[f'{prefix}_AGE_CRT_YEARS'] = features[f'{prefix}_AGE'] * features[f'{prefix}_CRT_YEARS']\n",
    "    features[f'{prefix}_VLU_RANK_CRT_YEARS'] = features[f'{prefix}_VLU_RANK'] * features[f'{prefix}_CRT_YEARS']\n",
    "    features[f'{prefix}_VLU_RANK_CARD_CNT'] = features[f'{prefix}_VLU_RANK'] * features[f'{prefix}_TOTAL_CARD_CNT']\n",
    "    features[f'{prefix}_AGE_CARD_CNT'] = features[f'{prefix}_AGE'] * features[f'{prefix}_TOTAL_CARD_CNT']\n",
    "    features[f'{prefix}_CRT_YEARS_CARD_CNT'] = features[f'{prefix}_CRT_YEARS'] * features[f'{prefix}_TOTAL_CARD_CNT']\n",
    "    \n",
    "    features[f'{prefix}_YOUNG_MALE'] = features[f'{prefix}_IS_YOUNG'] * features[f'{prefix}_IS_MALE']\n",
    "    features[f'{prefix}_YOUNG_FEMALE'] = features[f'{prefix}_IS_YOUNG'] * features[f'{prefix}_IS_FEMALE']\n",
    "    features[f'{prefix}_YOUNG_SINGLE'] = features[f'{prefix}_IS_YOUNG'] * features[f'{prefix}_IS_SINGLE']\n",
    "    features[f'{prefix}_MIDDLE_MARRIED'] = features[f'{prefix}_IS_MIDDLE'] * features[f'{prefix}_IS_MARRIED']\n",
    "    features[f'{prefix}_OLD_MARRIED'] = features[f'{prefix}_IS_OLD'] * features[f'{prefix}_IS_MARRIED']\n",
    "    features[f'{prefix}_HIGH_VLU_YOUNG'] = features[f'{prefix}_VLU_RANK_HIGH'] * features[f'{prefix}_IS_YOUNG']\n",
    "    features[f'{prefix}_HIGH_VLU_PRIME'] = features[f'{prefix}_VLU_RANK_HIGH'] * features[f'{prefix}_IS_PRIME_AGE']\n",
    "    features[f'{prefix}_HIGH_VLU_OLD_CUST'] = features[f'{prefix}_VLU_RANK_HIGH'] * features[f'{prefix}_IS_OLD_CUSTOMER']\n",
    "    features[f'{prefix}_HIGH_EDU_HIGH_VLU'] = features[f'{prefix}_EDU_HIGH'] * features[f'{prefix}_VLU_RANK_HIGH']\n",
    "    \n",
    "    drop_cols = ['DATA_DAT', 'AGE', 'IDV_CUST_SEX', 'IDV_CUST_MRGE_STS', 'IDV_CUST_HEDU', \n",
    "                 'IDV_CUST_CRT_TIME', 'IDV_CUST_VLU_RANK', 'HOLD_DCARD_CNT', 'HOLD_CCARD_CNT',\n",
    "                 'IDV_CUST_CRT_TIME_INT', f'{prefix}_CRT_DATE', f'{prefix}_MARRIAGE', f'{prefix}_EDU']\n",
    "    features = features.drop([col for col in drop_cols if col in features.columns], axis=1)\n",
    "    \n",
    "    print(f\"\\n处理后数据维度: {features.shape}\")\n",
    "    print(f\"新增特征数量: {features.shape[1] - 1}\")\n",
    "    \n",
    "    return features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 38,
   "id": "9ec71619",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "================================================================================\n",
      "处理自然属性信息表 - 训练集\n",
      "原始数据维度: (83391, 10)\n",
      "================================================================================\n",
      "\n",
      "1. 年龄特征处理\n",
      "2. 性别特征处理\n",
      "3. 婚姻状况特征处理\n",
      "4. 学历特征处理\n",
      "5. 客户建立时间特征处理\n",
      "6. 客户价值等级特征处理\n",
      "7. 持卡数量特征处理\n",
      "8. 年龄与建立时间交叉特征\n",
      "9. 组合交叉特征\n",
      "\n",
      "处理后数据维度: (83391, 78)\n",
      "新增特征数量: 77\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>NATURE_AGE</th>\n",
       "      <th>NATURE_AGE_SQUARE</th>\n",
       "      <th>NATURE_AGE_SQRT</th>\n",
       "      <th>NATURE_AGE_LOG</th>\n",
       "      <th>NATURE_AGE_GROUP_5</th>\n",
       "      <th>NATURE_AGE_GROUP_3</th>\n",
       "      <th>NATURE_IS_YOUNG</th>\n",
       "      <th>NATURE_IS_MIDDLE</th>\n",
       "      <th>NATURE_IS_OLD</th>\n",
       "      <th>...</th>\n",
       "      <th>NATURE_CRT_YEARS_CARD_CNT</th>\n",
       "      <th>NATURE_YOUNG_MALE</th>\n",
       "      <th>NATURE_YOUNG_FEMALE</th>\n",
       "      <th>NATURE_YOUNG_SINGLE</th>\n",
       "      <th>NATURE_MIDDLE_MARRIED</th>\n",
       "      <th>NATURE_OLD_MARRIED</th>\n",
       "      <th>NATURE_HIGH_VLU_YOUNG</th>\n",
       "      <th>NATURE_HIGH_VLU_PRIME</th>\n",
       "      <th>NATURE_HIGH_VLU_OLD_CUST</th>\n",
       "      <th>NATURE_HIGH_EDU_HIGH_VLU</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>ee39a885c34dc7b3ef1d6078368608c4</td>\n",
       "      <td>38</td>\n",
       "      <td>1444</td>\n",
       "      <td>6.164414</td>\n",
       "      <td>3.663562</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>14</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>8af0ca419efe285afe630122f1bc668c</td>\n",
       "      <td>43</td>\n",
       "      <td>1849</td>\n",
       "      <td>6.557439</td>\n",
       "      <td>3.784190</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>34</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>4f5882570aa441c90fbecef4a1cba1a3</td>\n",
       "      <td>47</td>\n",
       "      <td>2209</td>\n",
       "      <td>6.855655</td>\n",
       "      <td>3.871201</td>\n",
       "      <td>4</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>8</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>11e1109832dfab1ee347acf2f8f12a30</td>\n",
       "      <td>32</td>\n",
       "      <td>1024</td>\n",
       "      <td>5.656854</td>\n",
       "      <td>3.496508</td>\n",
       "      <td>2</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>14</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>6efb73cba2d64ac1c4b1cc0541b9f8b8</td>\n",
       "      <td>44</td>\n",
       "      <td>1936</td>\n",
       "      <td>6.633250</td>\n",
       "      <td>3.806662</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>14</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 78 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  NATURE_AGE  NATURE_AGE_SQUARE  \\\n",
       "0  ee39a885c34dc7b3ef1d6078368608c4          38               1444   \n",
       "1  8af0ca419efe285afe630122f1bc668c          43               1849   \n",
       "2  4f5882570aa441c90fbecef4a1cba1a3          47               2209   \n",
       "3  11e1109832dfab1ee347acf2f8f12a30          32               1024   \n",
       "4  6efb73cba2d64ac1c4b1cc0541b9f8b8          44               1936   \n",
       "\n",
       "   NATURE_AGE_SQRT  NATURE_AGE_LOG  NATURE_AGE_GROUP_5  NATURE_AGE_GROUP_3  \\\n",
       "0         6.164414        3.663562                   3                   2   \n",
       "1         6.557439        3.784190                   3                   2   \n",
       "2         6.855655        3.871201                   4                   2   \n",
       "3         5.656854        3.496508                   2                   2   \n",
       "4         6.633250        3.806662                   3                   2   \n",
       "\n",
       "   NATURE_IS_YOUNG  NATURE_IS_MIDDLE  NATURE_IS_OLD  ...  \\\n",
       "0                0                 1              0  ...   \n",
       "1                0                 1              0  ...   \n",
       "2                0                 1              0  ...   \n",
       "3                0                 1              0  ...   \n",
       "4                0                 1              0  ...   \n",
       "\n",
       "   NATURE_CRT_YEARS_CARD_CNT  NATURE_YOUNG_MALE  NATURE_YOUNG_FEMALE  \\\n",
       "0                         14                  0                    0   \n",
       "1                         34                  0                    0   \n",
       "2                          8                  0                    0   \n",
       "3                         14                  0                    0   \n",
       "4                         14                  0                    0   \n",
       "\n",
       "   NATURE_YOUNG_SINGLE  NATURE_MIDDLE_MARRIED  NATURE_OLD_MARRIED  \\\n",
       "0                    0                      0                   0   \n",
       "1                    0                      0                   0   \n",
       "2                    0                      0                   0   \n",
       "3                    0                      0                   0   \n",
       "4                    0                      0                   0   \n",
       "\n",
       "   NATURE_HIGH_VLU_YOUNG  NATURE_HIGH_VLU_PRIME  NATURE_HIGH_VLU_OLD_CUST  \\\n",
       "0                      0                      1                         1   \n",
       "1                      0                      0                         0   \n",
       "2                      0                      0                         0   \n",
       "3                      0                      1                         1   \n",
       "4                      0                      1                         1   \n",
       "\n",
       "   NATURE_HIGH_EDU_HIGH_VLU  \n",
       "0                         0  \n",
       "1                         0  \n",
       "2                         0  \n",
       "3                         0  \n",
       "4                         0  \n",
       "\n",
       "[5 rows x 78 columns]"
      ]
     },
     "execution_count": 38,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_NATURE_features = process_nature_features(TRAIN_NATURE_data, is_train=True)\n",
    "TRAIN_NATURE_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "747396b6",
   "metadata": {},
   "source": [
    "## 2. 资产负债表特征工程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 24,
   "id": "50fdfc39",
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_asset_features(df, is_train=True):\n",
    "    \"\"\"\n",
    "    资产负债表特征工程\n",
    "    字段包含: 金融资产、AUM、存款、贷款、理财、基金、保险等\n",
    "    \"\"\"\n",
    "    features = df.copy()\n",
    "    prefix = 'ASSET'\n",
    "    \n",
    "    print(f\"\\n{'='*80}\")\n",
    "    print(f\"处理资产负债表 - {'训练集' if is_train else '测试集'}\")\n",
    "    print(f\"原始数据维度: {features.shape}\")\n",
    "    print(f\"{'='*80}\\n\")\n",
    "    \n",
    "    if 'DATA_DAT' in features.columns:\n",
    "        features = features.drop('DATA_DAT', axis=1)\n",
    "    \n",
    "    numeric_cols = features.select_dtypes(include=[np.number]).columns.tolist()\n",
    "    if 'CUST_NO' in numeric_cols:\n",
    "        numeric_cols.remove('CUST_NO')\n",
    "    for col in numeric_cols:\n",
    "        features[col] = features[col].fillna(0)\n",
    "    \n",
    "    print(\"1. 金融资产特征工程\")\n",
    "    features[f'{prefix}_FA_GROWTH_3M'] = features['FA_BAL'] - features['FA_BAL_LST_3_MTH']\n",
    "    features[f'{prefix}_FA_GROWTH_6M'] = features['FA_BAL'] - features['FA_BAL_LST_6_MTH']\n",
    "    features[f'{prefix}_FA_GROWTH_12M'] = features['FA_BAL'] - features['FA_BAL_LST_12_MTH']\n",
    "    features[f'{prefix}_FA_RATIO_3M'] = features['FA_BAL'] / (features['FA_BAL_LST_3_MTH'] + 1)\n",
    "    features[f'{prefix}_FA_RATIO_6M'] = features['FA_BAL'] / (features['FA_BAL_LST_6_MTH'] + 1)\n",
    "    features[f'{prefix}_FA_RATIO_12M'] = features['FA_BAL'] / (features['FA_BAL_LST_12_MTH'] + 1)\n",
    "    features[f'{prefix}_FA_MAVER_RATIO'] = features['FA_MAVER_BAL'] / (features['FA_BAL'] + 1)\n",
    "    features[f'{prefix}_FA_IS_GROWING'] = (features[f'{prefix}_FA_GROWTH_3M'] > 0).astype(int)\n",
    "    features[f'{prefix}_FA_IS_STABLE'] = (abs(features[f'{prefix}_FA_GROWTH_3M']) < features['FA_BAL'] * 0.1).astype(int)\n",
    "    features[f'{prefix}_FA_GROWTH_RATE_3M'] = features[f'{prefix}_FA_GROWTH_3M'] / (features['FA_BAL_LST_3_MTH'] + 1)\n",
    "    features[f'{prefix}_FA_GROWTH_RATE_6M'] = features[f'{prefix}_FA_GROWTH_6M'] / (features['FA_BAL_LST_6_MTH'] + 1)\n",
    "    \n",
    "    print(\"2. AUM特征工程\")\n",
    "    features[f'{prefix}_AUM_GROWTH_3M'] = features['AUM_BAL'] - features['AUM_BAL_LST_3_MTH']\n",
    "    features[f'{prefix}_AUM_GROWTH_6M'] = features['AUM_BAL'] - features['AUM_BAL_LST_6_MTH']\n",
    "    features[f'{prefix}_AUM_GROWTH_12M'] = features['AUM_BAL'] - features['AUM_BAL_LST_12_MTH']\n",
    "    features[f'{prefix}_AUM_RATIO_3M'] = features['AUM_BAL'] / (features['AUM_BAL_LST_3_MTH'] + 1)\n",
    "    features[f'{prefix}_AUM_RATIO_6M'] = features['AUM_BAL'] / (features['AUM_BAL_LST_6_MTH'] + 1)\n",
    "    features[f'{prefix}_AUM_RATIO_12M'] = features['AUM_BAL'] / (features['AUM_BAL_LST_12_MTH'] + 1)\n",
    "    features[f'{prefix}_AUM_RATIO_MAX'] = features['AUM_BAL'] / (features['AUM_BAL_MAX'] + 1)\n",
    "    features[f'{prefix}_AUM_MAVER_RATIO'] = features['AUM_MAVER_BAL'] / (features['AUM_BAL'] + 1)\n",
    "    features[f'{prefix}_AUM_IS_GROWING'] = (features[f'{prefix}_AUM_GROWTH_3M'] > 0).astype(int)\n",
    "    features[f'{prefix}_AUM_IS_HIGH'] = (features['AUM_BAL'] > features['AUM_BAL_LST_12_MTH']).astype(int)\n",
    "    features[f'{prefix}_AUM_GROWTH_RATE_3M'] = features[f'{prefix}_AUM_GROWTH_3M'] / (features['AUM_BAL_LST_3_MTH'] + 1)\n",
    "    \n",
    "    print(\"3. 存款特征工程\")\n",
    "    features[f'{prefix}_DP_GROWTH_3M'] = features['DP_BAL'] - features['DP_BAL_LST_3_MTH']\n",
    "    features[f'{prefix}_DP_GROWTH_6M'] = features['DP_BAL'] - features['DP_BAL_LST_6_MTH']\n",
    "    features[f'{prefix}_DP_GROWTH_12M'] = features['DP_BAL'] - features['DP_BAL_LST_12_MTH']\n",
    "    features[f'{prefix}_DP_RATIO_3M'] = features['DP_BAL'] / (features['DP_BAL_LST_3_MTH'] + 1)\n",
    "    features[f'{prefix}_DP_IS_GROWING'] = (features[f'{prefix}_DP_GROWTH_3M'] > 0).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_DPSA_GROWTH_3M'] = features['DPSA_BAL'] - features['DPSA_BAL_LST_3_MTH']\n",
    "    features[f'{prefix}_DPSA_GROWTH_6M'] = features['DPSA_BAL'] - features['DPSA_BAL_LST_6_MTH']\n",
    "    features[f'{prefix}_DPSA_GROWTH_12M'] = features['DPSA_BAL'] - features['DPSA_BAL_LST_12_MTH']\n",
    "    \n",
    "    features[f'{prefix}_TD_BAL'] = features['DP_BAL'] - features['DPSA_BAL']\n",
    "    features[f'{prefix}_TD_RATIO'] = features[f'{prefix}_TD_BAL'] / (features['DP_BAL'] + 1)\n",
    "    features[f'{prefix}_DPSA_RATIO'] = features['DPSA_BAL'] / (features['DP_BAL'] + 1)\n",
    "    \n",
    "    print(\"4. 贷款特征工程\")\n",
    "    features[f'{prefix}_LOAN_GROWTH_3M'] = features['LOAN_BAL'] - features['LOAN_BAL_LST_3_MTH']\n",
    "    features[f'{prefix}_LOAN_GROWTH_6M'] = features['LOAN_BAL'] - features['LOAN_BAL_LST_6_MTH']\n",
    "    features[f'{prefix}_LOAN_MAVER_RATIO'] = features['LOAN_MAVER_BAL'] / (features['LOAN_BAL'] + 1)\n",
    "    features[f'{prefix}_HAS_LOAN'] = (features['LOAN_BAL'] > 0).astype(int)\n",
    "    features[f'{prefix}_LOAN_IS_GROWING'] = (features[f'{prefix}_LOAN_GROWTH_3M'] > 0).astype(int)\n",
    "    \n",
    "    print(\"5. 理财特征工程\")\n",
    "    features[f'{prefix}_FNCG_MAVER_RATIO'] = features['FNCG_MAVER_BAL'] / (features['FNCG_BAL'] + 1)\n",
    "    features[f'{prefix}_FNCG_YAVER_RATIO'] = features['FNCG_YAVER_BAL'] / (features['FNCG_BAL'] + 1)\n",
    "    features[f'{prefix}_HAS_FNCG'] = (features['FNCG_BAL'] > 0).astype(int)\n",
    "    \n",
    "    print(\"6. 基金特征工程\")\n",
    "    features[f'{prefix}_FUND_MAVER_RATIO'] = features['FUND_MAVER_BAL'] / (features['FUND_BAL'] + 1)\n",
    "    features[f'{prefix}_FUND_YAVER_RATIO'] = features['FUND_YAVER_BAL'] / (features['FUND_BAL'] + 1)\n",
    "    features[f'{prefix}_HAS_FUND'] = (features['FUND_BAL'] > 0).astype(int)\n",
    "    \n",
    "    print(\"7. 保险特征工程\")\n",
    "    features[f'{prefix}_INSUR_MAVER_RATIO'] = features['INSUR_MAVER_BAL'] / (features['INSUR_BAL'] + 1)\n",
    "    features[f'{prefix}_INSUR_YAVER_RATIO'] = features['INSUR_YAVER_BAL'] / (features['INSUR_BAL'] + 1)\n",
    "    features[f'{prefix}_HAS_INSUR'] = (features['INSUR_BAL'] > 0).astype(int)\n",
    "    \n",
    "    print(\"8. 资产配置组合特征\")\n",
    "    features[f'{prefix}_INVEST_BAL'] = features['FNCG_BAL'] + features['FUND_BAL'] + features['INSUR_BAL']\n",
    "    features[f'{prefix}_FNCG_RATIO'] = features['FNCG_BAL'] / (features['AUM_BAL'] + 1)\n",
    "    features[f'{prefix}_FUND_RATIO'] = features['FUND_BAL'] / (features['AUM_BAL'] + 1)\n",
    "    features[f'{prefix}_INSUR_RATIO'] = features['INSUR_BAL'] / (features['AUM_BAL'] + 1)\n",
    "    features[f'{prefix}_INVEST_RATIO'] = features[f'{prefix}_INVEST_BAL'] / (features['AUM_BAL'] + 1)\n",
    "    features[f'{prefix}_DP_RATIO'] = features['DP_BAL'] / (features['AUM_BAL'] + 1)\n",
    "    features[f'{prefix}_INVEST_CNT'] = features[f'{prefix}_HAS_FNCG'] + features[f'{prefix}_HAS_FUND'] + features[f'{prefix}_HAS_INSUR']\n",
    "    features[f'{prefix}_HAS_DIVERSIFIED'] = (features[f'{prefix}_INVEST_CNT'] >= 2).astype(int)\n",
    "    \n",
    "    print(\"9. 存贷比特征\")\n",
    "    features[f'{prefix}_DP_LOAN_RATIO'] = features['DP_BAL'] / (features['LOAN_BAL'] + 1)\n",
    "    features[f'{prefix}_DPSA_LOAN_RATIO'] = features['DPSA_BAL'] / (features['LOAN_BAL'] + 1)\n",
    "    features[f'{prefix}_AUM_LOAN_RATIO'] = features['AUM_BAL'] / (features['LOAN_BAL'] + 1)\n",
    "    features[f'{prefix}_LOAN_DP_RATIO'] = features['LOAN_BAL'] / (features['DP_BAL'] + 1)\n",
    "    \n",
    "    print(\"10. AUM与其他资产比例\")\n",
    "    features[f'{prefix}_AUM_FA_RATIO'] = features['AUM_BAL'] / (features['FA_BAL'] + 1)\n",
    "    features[f'{prefix}_AUM_DP_RATIO'] = features['AUM_BAL'] / (features['DP_BAL'] + 1)\n",
    "    features[f'{prefix}_FA_DP_RATIO'] = features['FA_BAL'] / (features['DP_BAL'] + 1)\n",
    "    \n",
    "    print(\"11. 资产集中度特征\")\n",
    "    total_bal = features['DP_BAL'] + features['FNCG_BAL'] + features['FUND_BAL'] + features['INSUR_BAL'] + 1\n",
    "    features[f'{prefix}_DP_CONCENTRATION'] = features['DP_BAL'] / total_bal\n",
    "    features[f'{prefix}_FNCG_CONCENTRATION'] = features['FNCG_BAL'] / total_bal\n",
    "    features[f'{prefix}_FUND_CONCENTRATION'] = features['FUND_BAL'] / total_bal\n",
    "    features[f'{prefix}_INSUR_CONCENTRATION'] = features['INSUR_BAL'] / total_bal\n",
    "    features[f'{prefix}_MAX_CONCENTRATION'] = features[[f'{prefix}_DP_CONCENTRATION', f'{prefix}_FNCG_CONCENTRATION', \n",
    "                                                        f'{prefix}_FUND_CONCENTRATION', f'{prefix}_INSUR_CONCENTRATION']].max(axis=1)\n",
    "    \n",
    "    print(\"12. 资产等级划分\")\n",
    "    features[f'{prefix}_FA_LEVEL'] = pd.qcut(features['FA_BAL'], q=5, duplicates='drop').cat.codes + 1\n",
    "    features[f'{prefix}_AUM_LEVEL'] = pd.qcut(features['AUM_BAL'], q=5, duplicates='drop').cat.codes + 1\n",
    "    features[f'{prefix}_DP_LEVEL'] = pd.qcut(features['DP_BAL'], q=5, duplicates='drop').cat.codes + 1\n",
    "    features[f'{prefix}_LOAN_LEVEL'] = pd.qcut(features['LOAN_BAL'], q=5, duplicates='drop').cat.codes + 1\n",
    "    \n",
    "    print(\"13. 高价值客户标识\")\n",
    "    features[f'{prefix}_IS_HIGH_FA'] = (features[f'{prefix}_FA_LEVEL'] >= 4).astype(int)\n",
    "    features[f'{prefix}_IS_HIGH_AUM'] = (features[f'{prefix}_AUM_LEVEL'] >= 4).astype(int)\n",
    "    features[f'{prefix}_IS_HIGH_DP'] = (features[f'{prefix}_DP_LEVEL'] >= 4).astype(int)\n",
    "    features[f'{prefix}_IS_HIGH_VALUE'] = ((features[f'{prefix}_IS_HIGH_FA'] == 1) | \n",
    "                                           (features[f'{prefix}_IS_HIGH_AUM'] == 1) | \n",
    "                                           (features[f'{prefix}_IS_HIGH_DP'] == 1)).astype(int)\n",
    "    \n",
    "    print(\"14. 零余额标识\")\n",
    "    features[f'{prefix}_IS_ZERO_FA'] = (features['FA_BAL'] == 0).astype(int)\n",
    "    features[f'{prefix}_IS_ZERO_AUM'] = (features['AUM_BAL'] == 0).astype(int)\n",
    "    features[f'{prefix}_IS_ZERO_DP'] = (features['DP_BAL'] == 0).astype(int)\n",
    "    features[f'{prefix}_IS_ZERO_LOAN'] = (features['LOAN_BAL'] == 0).astype(int)\n",
    "    \n",
    "    print(\"15. 对数变换特征\")\n",
    "    log_cols = ['FA_BAL', 'AUM_BAL', 'DP_BAL', 'DPSA_BAL', 'LOAN_BAL', 'FNCG_BAL', 'FUND_BAL', 'INSUR_BAL']\n",
    "    for col in log_cols:\n",
    "        if col in features.columns:\n",
    "            features[f'{prefix}_{col}_LOG'] = np.log1p(features[col])\n",
    "    \n",
    "    print(\"16. 月均、日均比值特征\")\n",
    "    features[f'{prefix}_FA_MAVER_DIV_BAL'] = features['FA_MAVER_BAL'] / (features['FA_BAL'] + 1)\n",
    "    features[f'{prefix}_AUM_MAVER_DIV_BAL'] = features['AUM_MAVER_BAL'] / (features['AUM_BAL'] + 1)\n",
    "    features[f'{prefix}_FNCG_MAVER_DIV_BAL'] = features['FNCG_MAVER_BAL'] / (features['FNCG_BAL'] + 1)\n",
    "    features[f'{prefix}_FUND_MAVER_DIV_BAL'] = features['FUND_MAVER_BAL'] / (features['FUND_BAL'] + 1)\n",
    "    features[f'{prefix}_INSUR_MAVER_DIV_BAL'] = features['INSUR_MAVER_BAL'] / (features['INSUR_BAL'] + 1)\n",
    "    \n",
    "    print(f\"\\n处理后数据维度: {features.shape}\")\n",
    "    print(f\"新增特征数量: {features.shape[1] - len(numeric_cols) - 1}\")\n",
    "    \n",
    "    return features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 25,
   "id": "f56d3b72",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "================================================================================\n",
      "处理资产负债表 - 训练集\n",
      "原始数据维度: (83391, 34)\n",
      "================================================================================\n",
      "\n",
      "1. 金融资产特征工程\n",
      "2. AUM特征工程\n",
      "3. 存款特征工程\n",
      "4. 贷款特征工程\n",
      "5. 理财特征工程\n",
      "6. 基金特征工程\n",
      "7. 保险特征工程\n",
      "8. 资产配置组合特征\n",
      "9. 存贷比特征\n",
      "10. AUM与其他资产比例\n",
      "11. 资产集中度特征\n",
      "12. 资产等级划分\n",
      "13. 高价值客户标识\n",
      "14. 零余额标识\n",
      "15. 对数变换特征\n",
      "16. 月均、日均比值特征\n",
      "\n",
      "处理后数据维度: (83391, 125)\n",
      "新增特征数量: 92\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>FA_BAL</th>\n",
       "      <th>FA_MAVER_BAL</th>\n",
       "      <th>FA_BAL_LST_3_MTH</th>\n",
       "      <th>FA_BAL_LST_6_MTH</th>\n",
       "      <th>FA_BAL_LST_12_MTH</th>\n",
       "      <th>AUM_BAL</th>\n",
       "      <th>AUM_MAVER_BAL</th>\n",
       "      <th>AUM_BAL_LST_3_MTH</th>\n",
       "      <th>AUM_BAL_LST_6_MTH</th>\n",
       "      <th>...</th>\n",
       "      <th>ASSET_DPSA_BAL_LOG</th>\n",
       "      <th>ASSET_LOAN_BAL_LOG</th>\n",
       "      <th>ASSET_FNCG_BAL_LOG</th>\n",
       "      <th>ASSET_FUND_BAL_LOG</th>\n",
       "      <th>ASSET_INSUR_BAL_LOG</th>\n",
       "      <th>ASSET_FA_MAVER_DIV_BAL</th>\n",
       "      <th>ASSET_AUM_MAVER_DIV_BAL</th>\n",
       "      <th>ASSET_FNCG_MAVER_DIV_BAL</th>\n",
       "      <th>ASSET_FUND_MAVER_DIV_BAL</th>\n",
       "      <th>ASSET_INSUR_MAVER_DIV_BAL</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>63b26174d0346c844e40cf2397d313f2</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.51</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>58.52</td>\n",
       "      <td>...</td>\n",
       "      <td>3.762594</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>3.542697</td>\n",
       "      <td>0.983199</td>\n",
       "      <td>0.983199</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.971065</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>a135f3861cdf0b6e5c63852c96cfb74e</td>\n",
       "      <td>1.76</td>\n",
       "      <td>4.91</td>\n",
       "      <td>6.46</td>\n",
       "      <td>11.33</td>\n",
       "      <td>10.94</td>\n",
       "      <td>1.76</td>\n",
       "      <td>4.91</td>\n",
       "      <td>6.46</td>\n",
       "      <td>11.33</td>\n",
       "      <td>...</td>\n",
       "      <td>0.951658</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.751416</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>1.778986</td>\n",
       "      <td>1.778986</td>\n",
       "      <td>0.528302</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>5ef963c2df624d4e65e4d0e50de64420</td>\n",
       "      <td>9.67</td>\n",
       "      <td>10.08</td>\n",
       "      <td>10.34</td>\n",
       "      <td>9.95</td>\n",
       "      <td>8.62</td>\n",
       "      <td>9.67</td>\n",
       "      <td>10.08</td>\n",
       "      <td>10.34</td>\n",
       "      <td>9.95</td>\n",
       "      <td>...</td>\n",
       "      <td>2.367436</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.944705</td>\n",
       "      <td>0.944705</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>6fdf44f63582ba96aea40060c54562ba</td>\n",
       "      <td>129.81</td>\n",
       "      <td>129.22</td>\n",
       "      <td>128.36</td>\n",
       "      <td>127.96</td>\n",
       "      <td>126.60</td>\n",
       "      <td>129.81</td>\n",
       "      <td>129.22</td>\n",
       "      <td>128.36</td>\n",
       "      <td>127.96</td>\n",
       "      <td>...</td>\n",
       "      <td>3.816613</td>\n",
       "      <td>0.0</td>\n",
       "      <td>4.141705</td>\n",
       "      <td>4.426641</td>\n",
       "      <td>3.411808</td>\n",
       "      <td>0.987845</td>\n",
       "      <td>0.987845</td>\n",
       "      <td>0.983786</td>\n",
       "      <td>0.989121</td>\n",
       "      <td>1.534960</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>6efb73cba2d64ac1c4b1cc0541b9f8b8</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>0.92</td>\n",
       "      <td>...</td>\n",
       "      <td>0.652325</td>\n",
       "      <td>0.0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.479167</td>\n",
       "      <td>0.479167</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 125 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  FA_BAL  FA_MAVER_BAL  FA_BAL_LST_3_MTH  \\\n",
       "0  63b26174d0346c844e40cf2397d313f2   58.52         58.52             58.52   \n",
       "1  a135f3861cdf0b6e5c63852c96cfb74e    1.76          4.91              6.46   \n",
       "2  5ef963c2df624d4e65e4d0e50de64420    9.67         10.08             10.34   \n",
       "3  6fdf44f63582ba96aea40060c54562ba  129.81        129.22            128.36   \n",
       "4  6efb73cba2d64ac1c4b1cc0541b9f8b8    0.92          0.92              0.92   \n",
       "\n",
       "   FA_BAL_LST_6_MTH  FA_BAL_LST_12_MTH  AUM_BAL  AUM_MAVER_BAL  \\\n",
       "0             58.52              58.51    58.52          58.52   \n",
       "1             11.33              10.94     1.76           4.91   \n",
       "2              9.95               8.62     9.67          10.08   \n",
       "3            127.96             126.60   129.81         129.22   \n",
       "4              0.92               0.92     0.92           0.92   \n",
       "\n",
       "   AUM_BAL_LST_3_MTH  AUM_BAL_LST_6_MTH  ...  ASSET_DPSA_BAL_LOG  \\\n",
       "0              58.52              58.52  ...            3.762594   \n",
       "1               6.46              11.33  ...            0.951658   \n",
       "2              10.34               9.95  ...            2.367436   \n",
       "3             128.36             127.96  ...            3.816613   \n",
       "4               0.92               0.92  ...            0.652325   \n",
       "\n",
       "   ASSET_LOAN_BAL_LOG  ASSET_FNCG_BAL_LOG  ASSET_FUND_BAL_LOG  \\\n",
       "0                 0.0            0.000000            0.000000   \n",
       "1                 0.0            0.751416            0.000000   \n",
       "2                 0.0            0.000000            0.000000   \n",
       "3                 0.0            4.141705            4.426641   \n",
       "4                 0.0            0.000000            0.000000   \n",
       "\n",
       "   ASSET_INSUR_BAL_LOG  ASSET_FA_MAVER_DIV_BAL  ASSET_AUM_MAVER_DIV_BAL  \\\n",
       "0             3.542697                0.983199                 0.983199   \n",
       "1             0.000000                1.778986                 1.778986   \n",
       "2             0.000000                0.944705                 0.944705   \n",
       "3             3.411808                0.987845                 0.987845   \n",
       "4             0.000000                0.479167                 0.479167   \n",
       "\n",
       "   ASSET_FNCG_MAVER_DIV_BAL  ASSET_FUND_MAVER_DIV_BAL  \\\n",
       "0                  0.000000                  0.000000   \n",
       "1                  0.528302                  0.000000   \n",
       "2                  0.000000                  0.000000   \n",
       "3                  0.983786                  0.989121   \n",
       "4                  0.000000                  0.000000   \n",
       "\n",
       "   ASSET_INSUR_MAVER_DIV_BAL  \n",
       "0                   0.971065  \n",
       "1                   0.000000  \n",
       "2                   0.000000  \n",
       "3                   1.534960  \n",
       "4                   0.000000  \n",
       "\n",
       "[5 rows x 125 columns]"
      ]
     },
     "execution_count": 25,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_ASSET_features = process_asset_features(TRAIN_ASSET_DEBT_data, is_train=True)\n",
    "TRAIN_ASSET_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "3be0517c",
   "metadata": {},
   "source": [
    "## 3. 产品持有信息表特征工程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 26,
   "id": "d8b5814e",
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_prod_hold_features(df, is_train=True):\n",
    "    \"\"\"\n",
    "    产品持有信息表特征工程\n",
    "    字段包含: 存款、贷款、借记卡、贷记卡、理财、基金、国债、保险、贵金属、三方支付等产品标识\n",
    "    \"\"\"\n",
    "    features = df.copy()\n",
    "    prefix = 'PROD'\n",
    "    \n",
    "    print(f\"\\n{'='*80}\")\n",
    "    print(f\"处理产品持有信息表 - {'训练集' if is_train else '测试集'}\")\n",
    "    print(f\"原始数据维度: {features.shape}\")\n",
    "    print(f\"{'='*80}\\n\")\n",
    "    \n",
    "    if 'DATA_DAT' in features.columns:\n",
    "        features = features.drop('DATA_DAT', axis=1)\n",
    "    \n",
    "    ind_cols = [col for col in features.columns if col.endswith('_IND') and col != 'CUST_NO']\n",
    "    for col in ind_cols:\n",
    "        features[col] = features[col].fillna(0).astype(int)\n",
    "    \n",
    "    print(\"1. 产品持有数量特征\")\n",
    "    features[f'{prefix}_TOTAL_PROD_CNT'] = features[ind_cols].sum(axis=1)\n",
    "    features[f'{prefix}_FINANCE_PROD_CNT'] = features[['FNCG_IND', 'FUND_IND', 'BOND_IND', 'INSUR_IND', 'GOLD_IND']].sum(axis=1)\n",
    "    features[f'{prefix}_BASIC_PROD_CNT'] = features[['DP_IND', 'DCARD_IND']].sum(axis=1)\n",
    "    features[f'{prefix}_CARD_CNT'] = features[['DCARD_IND', 'CCARD_IND']].sum(axis=1)\n",
    "    features[f'{prefix}_TPAY_CNT'] = features[['TPAY_DCARD_IND', 'TPAY_CCARD_IND', 'TPAY_WX_IND', 'TPAY_ALI_IND']].sum(axis=1)\n",
    "    \n",
    "    print(\"2. 产品持有标识特征\")\n",
    "    features[f'{prefix}_HAS_DP'] = features['DP_IND']\n",
    "    features[f'{prefix}_HAS_LOAN'] = features['LOAN_IND']\n",
    "    features[f'{prefix}_HAS_DCARD'] = features['DCARD_IND']\n",
    "    features[f'{prefix}_HAS_CCARD'] = features['CCARD_IND']\n",
    "    features[f'{prefix}_HAS_FNCG'] = features['FNCG_IND']\n",
    "    features[f'{prefix}_HAS_FUND'] = features['FUND_IND']\n",
    "    features[f'{prefix}_HAS_BOND'] = features['BOND_IND']\n",
    "    features[f'{prefix}_HAS_INSUR'] = features['INSUR_IND']\n",
    "    features[f'{prefix}_HAS_GOLD'] = features['GOLD_IND']\n",
    "    \n",
    "    print(\"3. 卡类产品组合特征\")\n",
    "    features[f'{prefix}_ONLY_DCARD'] = ((features['DCARD_IND'] == 1) & (features['CCARD_IND'] == 0)).astype(int)\n",
    "    features[f'{prefix}_ONLY_CCARD'] = ((features['DCARD_IND'] == 0) & (features['CCARD_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_BOTH_CARD'] = ((features['DCARD_IND'] == 1) & (features['CCARD_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_NO_CARD'] = ((features['DCARD_IND'] == 0) & (features['CCARD_IND'] == 0)).astype(int)\n",
    "    \n",
    "    print(\"4. 三方支付组合特征\")\n",
    "    features[f'{prefix}_HAS_TPAY_DCARD'] = features['TPAY_DCARD_IND']\n",
    "    features[f'{prefix}_HAS_TPAY_CCARD'] = features['TPAY_CCARD_IND']\n",
    "    features[f'{prefix}_HAS_TPAY_WX'] = features['TPAY_WX_IND']\n",
    "    features[f'{prefix}_HAS_TPAY_ALI'] = features['TPAY_ALI_IND']\n",
    "    features[f'{prefix}_TPAY_BOTH_CHANNEL'] = ((features['TPAY_WX_IND'] == 1) & (features['TPAY_ALI_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_TPAY_ONLY_WX'] = ((features['TPAY_WX_IND'] == 1) & (features['TPAY_ALI_IND'] == 0)).astype(int)\n",
    "    features[f'{prefix}_TPAY_ONLY_ALI'] = ((features['TPAY_WX_IND'] == 0) & (features['TPAY_ALI_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_TPAY_BIND_BOTH_CARD'] = ((features['TPAY_DCARD_IND'] == 1) & (features['TPAY_CCARD_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_TPAY_NO_BIND'] = ((features['TPAY_DCARD_IND'] == 0) & (features['TPAY_CCARD_IND'] == 0)).astype(int)\n",
    "    \n",
    "    print(\"5. 理财产品组合特征\")\n",
    "    features[f'{prefix}_INVEST_PROD_CNT'] = features[['FNCG_IND', 'FUND_IND', 'INSUR_IND']].sum(axis=1)\n",
    "    features[f'{prefix}_HAS_FNCG_FUND'] = ((features['FNCG_IND'] == 1) & (features['FUND_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_HAS_FNCG_INSUR'] = ((features['FNCG_IND'] == 1) & (features['INSUR_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_HAS_FUND_INSUR'] = ((features['FUND_IND'] == 1) & (features['INSUR_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_HAS_ALL_INVEST'] = ((features['FNCG_IND'] == 1) & (features['FUND_IND'] == 1) & (features['INSUR_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_ONLY_FNCG'] = ((features['FNCG_IND'] == 1) & (features['FUND_IND'] == 0) & (features['INSUR_IND'] == 0)).astype(int)\n",
    "    features[f'{prefix}_ONLY_FUND'] = ((features['FNCG_IND'] == 0) & (features['FUND_IND'] == 1) & (features['INSUR_IND'] == 0)).astype(int)\n",
    "    features[f'{prefix}_ONLY_INSUR'] = ((features['FNCG_IND'] == 0) & (features['FUND_IND'] == 0) & (features['INSUR_IND'] == 1)).astype(int)\n",
    "    \n",
    "    print(\"6. 存贷产品组合特征\")\n",
    "    features[f'{prefix}_HAS_DP_LOAN'] = ((features['DP_IND'] == 1) & (features['LOAN_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_ONLY_DP'] = ((features['DP_IND'] == 1) & (features['LOAN_IND'] == 0)).astype(int)\n",
    "    features[f'{prefix}_ONLY_LOAN'] = ((features['DP_IND'] == 0) & (features['LOAN_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_NO_DP_LOAN'] = ((features['DP_IND'] == 0) & (features['LOAN_IND'] == 0)).astype(int)\n",
    "    \n",
    "    print(\"7. 特殊产品标识\")\n",
    "    features[f'{prefix}_HAS_BOND_GOLD'] = ((features['BOND_IND'] == 1) | (features['GOLD_IND'] == 1)).astype(int)\n",
    "    features[f'{prefix}_HAS_ALL_FINANCE'] = (features[f'{prefix}_FINANCE_PROD_CNT'] == 5).astype(int)\n",
    "    features[f'{prefix}_HAS_BOND'] = features['BOND_IND']\n",
    "    features[f'{prefix}_HAS_GOLD'] = features['GOLD_IND']\n",
    "    \n",
    "    print(\"8. 客户价值分层特征\")\n",
    "    features[f'{prefix}_IS_HIGH_VALUE'] = (features[f'{prefix}_TOTAL_PROD_CNT'] >= 8).astype(int)\n",
    "    features[f'{prefix}_IS_MID_VALUE'] = ((features[f'{prefix}_TOTAL_PROD_CNT'] >= 5) & (features[f'{prefix}_TOTAL_PROD_CNT'] < 8)).astype(int)\n",
    "    features[f'{prefix}_IS_LOW_VALUE'] = (features[f'{prefix}_TOTAL_PROD_CNT'] < 5).astype(int)\n",
    "    \n",
    "    features[f'{prefix}_IS_INVEST_CUSTOMER'] = (features[f'{prefix}_INVEST_PROD_CNT'] >= 2).astype(int)\n",
    "    features[f'{prefix}_IS_DIGITAL_CUSTOMER'] = (features[f'{prefix}_TPAY_CNT'] >= 2).astype(int)\n",
    "    features[f'{prefix}_IS_COMPREHENSIVE'] = ((features[f'{prefix}_TOTAL_PROD_CNT'] >= 6) & \n",
    "                                               (features[f'{prefix}_FINANCE_PROD_CNT'] >= 2) & \n",
    "                                               (features[f'{prefix}_TPAY_CNT'] >= 1)).astype(int)\n",
    "    \n",
    "    print(\"9. 产品集中度特征\")\n",
    "    features[f'{prefix}_FINANCE_RATIO'] = features[f'{prefix}_FINANCE_PROD_CNT'] / (features[f'{prefix}_TOTAL_PROD_CNT'] + 1)\n",
    "    features[f'{prefix}_BASIC_RATIO'] = features[f'{prefix}_BASIC_PROD_CNT'] / (features[f'{prefix}_TOTAL_PROD_CNT'] + 1)\n",
    "    features[f'{prefix}_TPAY_RATIO'] = features[f'{prefix}_TPAY_CNT'] / (features[f'{prefix}_TOTAL_PROD_CNT'] + 1)\n",
    "    features[f'{prefix}_INVEST_RATIO'] = features[f'{prefix}_INVEST_PROD_CNT'] / (features[f'{prefix}_TOTAL_PROD_CNT'] + 1)\n",
    "    \n",
    "    print(\"10. 产品多样化程度\")\n",
    "    features[f'{prefix}_DIVERSITY_SCORE'] = (\n",
    "        features[f'{prefix}_HAS_DP'] + features[f'{prefix}_HAS_LOAN'] + \n",
    "        features[f'{prefix}_HAS_DCARD'] + features[f'{prefix}_HAS_CCARD'] + \n",
    "        features[f'{prefix}_HAS_FNCG'] + features[f'{prefix}_HAS_FUND'] + \n",
    "        features[f'{prefix}_HAS_INSUR'] + features[f'{prefix}_HAS_BOND'] + \n",
    "        features[f'{prefix}_HAS_GOLD']\n",
    "    )\n",
    "    \n",
    "    print(\"11. 组合交叉特征\")\n",
    "    features[f'{prefix}_CARD_LOAN'] = features[f'{prefix}_CARD_CNT'] * features['LOAN_IND']\n",
    "    features[f'{prefix}_CARD_FINANCE'] = features[f'{prefix}_CARD_CNT'] * features[f'{prefix}_FINANCE_PROD_CNT']\n",
    "    features[f'{prefix}_LOAN_FINANCE'] = features['LOAN_IND'] * features[f'{prefix}_FINANCE_PROD_CNT']\n",
    "    features[f'{prefix}_TPAY_FINANCE'] = features[f'{prefix}_TPAY_CNT'] * features[f'{prefix}_FINANCE_PROD_CNT']\n",
    "    features[f'{prefix}_CARD_TPAY'] = features[f'{prefix}_CARD_CNT'] * features[f'{prefix}_TPAY_CNT']\n",
    "    features[f'{prefix}_DP_FINANCE'] = features['DP_IND'] * features[f'{prefix}_FINANCE_PROD_CNT']\n",
    "    \n",
    "    print(\"12. 信用卡相关特征\")\n",
    "    features[f'{prefix}_CCARD_TPAY_BIND'] = features['CCARD_IND'] * features['TPAY_CCARD_IND']\n",
    "    features[f'{prefix}_DCARD_TPAY_BIND'] = features['DCARD_IND'] * features['TPAY_DCARD_IND']\n",
    "    features[f'{prefix}_CCARD_NO_TPAY'] = ((features['CCARD_IND'] == 1) & (features['TPAY_CCARD_IND'] == 0)).astype(int)\n",
    "    \n",
    "    print(f\"\\n处理后数据维度: {features.shape}\")\n",
    "    print(f\"新增特征数量: {features.shape[1] - len(ind_cols) - 1}\")\n",
    "    \n",
    "    return features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 27,
   "id": "0345ef9d",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "================================================================================\n",
      "处理产品持有信息表 - 训练集\n",
      "原始数据维度: (83391, 15)\n",
      "================================================================================\n",
      "\n",
      "1. 产品持有数量特征\n",
      "2. 产品持有标识特征\n",
      "3. 卡类产品组合特征\n",
      "4. 三方支付组合特征\n",
      "5. 理财产品组合特征\n",
      "6. 存贷产品组合特征\n",
      "7. 特殊产品标识\n",
      "8. 客户价值分层特征\n",
      "9. 产品集中度特征\n",
      "10. 产品多样化程度\n",
      "11. 组合交叉特征\n",
      "12. 信用卡相关特征\n",
      "\n",
      "处理后数据维度: (83391, 75)\n",
      "新增特征数量: 61\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>DP_IND</th>\n",
       "      <th>LOAN_IND</th>\n",
       "      <th>DCARD_IND</th>\n",
       "      <th>CCARD_IND</th>\n",
       "      <th>FNCG_IND</th>\n",
       "      <th>FUND_IND</th>\n",
       "      <th>BOND_IND</th>\n",
       "      <th>INSUR_IND</th>\n",
       "      <th>GOLD_IND</th>\n",
       "      <th>...</th>\n",
       "      <th>PROD_DIVERSITY_SCORE</th>\n",
       "      <th>PROD_CARD_LOAN</th>\n",
       "      <th>PROD_CARD_FINANCE</th>\n",
       "      <th>PROD_LOAN_FINANCE</th>\n",
       "      <th>PROD_TPAY_FINANCE</th>\n",
       "      <th>PROD_CARD_TPAY</th>\n",
       "      <th>PROD_DP_FINANCE</th>\n",
       "      <th>PROD_CCARD_TPAY_BIND</th>\n",
       "      <th>PROD_DCARD_TPAY_BIND</th>\n",
       "      <th>PROD_CCARD_NO_TPAY</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>ee39a885c34dc7b3ef1d6078368608c4</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>6f196431815ad6541a894fdfbba9d5d6</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>4f5882570aa441c90fbecef4a1cba1a3</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>3</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>efd3e688ffbffda9635a3bf65b04facf</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>6efb73cba2d64ac1c4b1cc0541b9f8b8</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 75 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  DP_IND  LOAN_IND  DCARD_IND  CCARD_IND  \\\n",
       "0  ee39a885c34dc7b3ef1d6078368608c4       1         0          1          0   \n",
       "1  6f196431815ad6541a894fdfbba9d5d6       1         0          1          0   \n",
       "2  4f5882570aa441c90fbecef4a1cba1a3       1         0          1          0   \n",
       "3  efd3e688ffbffda9635a3bf65b04facf       1         0          1          0   \n",
       "4  6efb73cba2d64ac1c4b1cc0541b9f8b8       1         0          1          0   \n",
       "\n",
       "   FNCG_IND  FUND_IND  BOND_IND  INSUR_IND  GOLD_IND  ...  \\\n",
       "0         0         0         0          0         0  ...   \n",
       "1         0         0         0          0         0  ...   \n",
       "2         0         0         0          0         0  ...   \n",
       "3         0         0         0          0         0  ...   \n",
       "4         0         0         0          0         0  ...   \n",
       "\n",
       "   PROD_DIVERSITY_SCORE  PROD_CARD_LOAN  PROD_CARD_FINANCE  PROD_LOAN_FINANCE  \\\n",
       "0                     2               0                  0                  0   \n",
       "1                     2               0                  0                  0   \n",
       "2                     2               0                  0                  0   \n",
       "3                     2               0                  0                  0   \n",
       "4                     2               0                  0                  0   \n",
       "\n",
       "   PROD_TPAY_FINANCE  PROD_CARD_TPAY  PROD_DP_FINANCE  PROD_CCARD_TPAY_BIND  \\\n",
       "0                  0               2                0                     0   \n",
       "1                  0               3                0                     0   \n",
       "2                  0               3                0                     0   \n",
       "3                  0               2                0                     0   \n",
       "4                  0               0                0                     0   \n",
       "\n",
       "   PROD_DCARD_TPAY_BIND  PROD_CCARD_NO_TPAY  \n",
       "0                     1                   0  \n",
       "1                     1                   0  \n",
       "2                     1                   0  \n",
       "3                     1                   0  \n",
       "4                     0                   0  \n",
       "\n",
       "[5 rows x 75 columns]"
      ]
     },
     "execution_count": 27,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_PROD_features = process_prod_hold_features(TRAIN_PROD_HOLD_data, is_train=True)\n",
    "TRAIN_PROD_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "7a27e027",
   "metadata": {},
   "source": [
    "## 4. 掌银客户信息表特征工程"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 36,
   "id": "44e90f2d",
   "metadata": {},
   "outputs": [],
   "source": [
    "def process_mb_cust_info_features(df, is_train=True):\n",
    "    \"\"\"\n",
    "    处理掌银客户信息表特征\n",
    "    \n",
    "    参数:\n",
    "        df: 原始数据DataFrame\n",
    "        is_train: 是否为训练集,默认True\n",
    "    \n",
    "    返回:\n",
    "        处理后的特征DataFrame (只保留CUST_NO和新特征,删除原始字段)\n",
    "    \"\"\"\n",
    "    prefix = 'MB'\n",
    "    features = df.copy()\n",
    "    \n",
    "    print(\"=\" * 80)\n",
    "    print(f\"处理掌银客户信息表 - {'训练集' if is_train else '测试集'}\")\n",
    "    print(f\"原始数据维度: {features.shape}\")\n",
    "    print(\"=\" * 80)\n",
    "    print()\n",
    "    \n",
    "    # 1. 掌银注册时间特征工程 - 正确处理float64→int→datetime\n",
    "    print(\"1. 掌银注册时间特征工程(MB_REG_TIME)\")\n",
    "    if 'MB_REG_TIME' in features.columns:\n",
    "        # 步骤1: float64 → int (去除小数部分,如20131231.0 → 20131231)\n",
    "        reg_time_int = features['MB_REG_TIME'].fillna(0).astype(int)\n",
    "        features[f'{prefix}_HAS_REG'] = (reg_time_int > 0).astype(int)\n",
    "        \n",
    "        # 步骤2: int → datetime (转换为日期类型)\n",
    "        reg_date = pd.to_datetime(\n",
    "            reg_time_int.astype(str),\n",
    "            format='%Y%m%d',\n",
    "            errors='coerce'\n",
    "        )\n",
    "        \n",
    "        # 参考日期(DATA_DAT = 20131231)\n",
    "        ref_date = pd.to_datetime('20131231', format='%Y%m%d')\n",
    "        \n",
    "        # 1.1 注册时长特征(多维度)\n",
    "        reg_tenure_days = (ref_date - reg_date).dt.days\n",
    "        features[f'{prefix}_REG_TENURE_DAYS'] = reg_tenure_days.fillna(0).astype(int)\n",
    "        features[f'{prefix}_REG_TENURE_WEEKS'] = (features[f'{prefix}_REG_TENURE_DAYS'] / 7).astype(int)\n",
    "        features[f'{prefix}_REG_TENURE_MONTHS'] = (features[f'{prefix}_REG_TENURE_DAYS'] / 30).astype(int)\n",
    "        features[f'{prefix}_REG_TENURE_QUARTERS'] = (features[f'{prefix}_REG_TENURE_DAYS'] / 90).astype(int)\n",
    "        features[f'{prefix}_REG_TENURE_YEARS'] = (features[f'{prefix}_REG_TENURE_DAYS'] / 365).astype(int)\n",
    "        \n",
    "        # 1.2 注册时长数学变换\n",
    "        features[f'{prefix}_REG_TENURE_LOG'] = np.log1p(features[f'{prefix}_REG_TENURE_DAYS'])\n",
    "        features[f'{prefix}_REG_TENURE_SQRT'] = np.sqrt(features[f'{prefix}_REG_TENURE_DAYS'])\n",
    "        features[f'{prefix}_REG_TENURE_SQUARE'] = features[f'{prefix}_REG_TENURE_DAYS'] ** 2\n",
    "        \n",
    "        # 1.3 注册时间解构特征\n",
    "        features[f'{prefix}_REG_YEAR'] = reg_date.dt.year.fillna(0).astype(int)\n",
    "        features[f'{prefix}_REG_MONTH'] = reg_date.dt.month.fillna(0).astype(int)\n",
    "        features[f'{prefix}_REG_QUARTER'] = reg_date.dt.quarter.fillna(0).astype(int)\n",
    "        features[f'{prefix}_REG_DAYOFYEAR'] = reg_date.dt.dayofyear.fillna(0).astype(int)\n",
    "        features[f'{prefix}_REG_WEEKOFYEAR'] = reg_date.dt.isocalendar().week.fillna(0).astype(int)\n",
    "        features[f'{prefix}_REG_DAYOFWEEK'] = reg_date.dt.dayofweek.fillna(0).astype(int)\n",
    "        \n",
    "        # 1.4 注册季度独热编码\n",
    "        features[f'{prefix}_REG_IS_Q1'] = (features[f'{prefix}_REG_QUARTER'] == 1).astype(int)\n",
    "        features[f'{prefix}_REG_IS_Q2'] = (features[f'{prefix}_REG_QUARTER'] == 2).astype(int)\n",
    "        features[f'{prefix}_REG_IS_Q3'] = (features[f'{prefix}_REG_QUARTER'] == 3).astype(int)\n",
    "        features[f'{prefix}_REG_IS_Q4'] = (features[f'{prefix}_REG_QUARTER'] == 4).astype(int)\n",
    "        \n",
    "        # 1.5 注册时段标识\n",
    "        features[f'{prefix}_REG_IS_YEAR_START'] = (features[f'{prefix}_REG_MONTH'] <= 2).astype(int)\n",
    "        features[f'{prefix}_REG_IS_YEAR_MID'] = ((features[f'{prefix}_REG_MONTH'] >= 5) & (features[f'{prefix}_REG_MONTH'] <= 8)).astype(int)\n",
    "        features[f'{prefix}_REG_IS_YEAR_END'] = (features[f'{prefix}_REG_MONTH'] >= 11).astype(int)\n",
    "        features[f'{prefix}_REG_IS_WEEKEND'] = (reg_date.dt.dayofweek >= 5).fillna(0).astype(int)\n",
    "        \n",
    "        # 1.6 注册时长分组(6级:新用户→老用户)\n",
    "        reg_tenure_bins_6 = pd.cut(\n",
    "            features[f'{prefix}_REG_TENURE_MONTHS'],\n",
    "            bins=[-1, 3, 6, 12, 24, 60, 10000],\n",
    "            labels=[1, 2, 3, 4, 5, 6]\n",
    "        )\n",
    "        features[f'{prefix}_REG_TENURE_GROUP_6'] = reg_tenure_bins_6.cat.codes + 1\n",
    "        \n",
    "        # 1.7 注册时长分组(3级:简化版)\n",
    "        reg_tenure_bins_3 = pd.cut(\n",
    "            features[f'{prefix}_REG_TENURE_MONTHS'],\n",
    "            bins=[-1, 6, 24, 10000],\n",
    "            labels=[1, 2, 3]\n",
    "        )\n",
    "        features[f'{prefix}_REG_TENURE_GROUP_3'] = reg_tenure_bins_3.cat.codes + 1\n",
    "        \n",
    "        # 1.8 用户生命周期阶段标识\n",
    "        features[f'{prefix}_IS_NEW_REG'] = (features[f'{prefix}_REG_TENURE_MONTHS'] <= 3).astype(int)\n",
    "        features[f'{prefix}_IS_GROWING'] = ((features[f'{prefix}_REG_TENURE_MONTHS'] > 3) & (features[f'{prefix}_REG_TENURE_MONTHS'] <= 12)).astype(int)\n",
    "        features[f'{prefix}_IS_MATURE'] = ((features[f'{prefix}_REG_TENURE_MONTHS'] > 12) & (features[f'{prefix}_REG_TENURE_MONTHS'] <= 36)).astype(int)\n",
    "        features[f'{prefix}_IS_OLD_USER'] = (features[f'{prefix}_REG_TENURE_YEARS'] >= 3).astype(int)\n",
    "        \n",
    "        # 1.9 注册年份相对值\n",
    "        min_year = features[f'{prefix}_REG_YEAR'].replace(0, features[f'{prefix}_REG_YEAR'].max()).min()\n",
    "        features[f'{prefix}_REG_YEAR_RELATIVE'] = features[f'{prefix}_REG_YEAR'] - min_year\n",
    "        features[f'{prefix}_REG_YEAR_RELATIVE'] = features[f'{prefix}_REG_YEAR_RELATIVE'].clip(lower=0)\n",
    "    \n",
    "    print(\"2. 掌银客户类型特征工程(MB_CUST_TYPE)\")\n",
    "    if 'MB_CUST_TYPE' in features.columns:\n",
    "        # MB_CUST_TYPE是字符串类型(hash值),需要标签编码\n",
    "        type_mapping = {val: idx for idx, val in enumerate(sorted(features['MB_CUST_TYPE'].dropna().unique()))}\n",
    "        features[f'{prefix}_CUST_TYPE_ENCODE'] = features['MB_CUST_TYPE'].map(type_mapping).fillna(-1).astype(int)\n",
    "        features[f'{prefix}_HAS_CUST_TYPE'] = features['MB_CUST_TYPE'].notna().astype(int)\n",
    "        \n",
    "        # 每个类型的客户数量\n",
    "        type_count = features.groupby('MB_CUST_TYPE')['MB_CUST_TYPE'].transform('count')\n",
    "        features[f'{prefix}_CUST_TYPE_COUNT'] = type_count.fillna(0).astype(int)\n",
    "    \n",
    "    print(\"3. 登录天数特征工程(MB_LOGIN_CNT)\")\n",
    "    features[f'{prefix}_LOGIN_1M'] = features['MB_LOGIN_CNT_1M'].fillna(0)\n",
    "    features[f'{prefix}_LOGIN_3M'] = features['MB_LOGIN_CNT_3M'].fillna(0)\n",
    "    \n",
    "    # 登录增长率和趋势\n",
    "    features[f'{prefix}_LOGIN_AVG_3M'] = features[f'{prefix}_LOGIN_3M'] / 3\n",
    "    features[f'{prefix}_LOGIN_RATIO_1M_3M'] = features[f'{prefix}_LOGIN_1M'] / (features[f'{prefix}_LOGIN_AVG_3M'] + 1)\n",
    "    features[f'{prefix}_LOGIN_GROWTH'] = features[f'{prefix}_LOGIN_1M'] - features[f'{prefix}_LOGIN_AVG_3M']\n",
    "    features[f'{prefix}_LOGIN_IS_STABLE'] = (abs(features[f'{prefix}_LOGIN_GROWTH']) < 1).astype(int)\n",
    "    features[f'{prefix}_LOGIN_IS_INCREASING'] = (features[f'{prefix}_LOGIN_GROWTH'] > 0).astype(int)\n",
    "    features[f'{prefix}_LOGIN_IS_DECREASING'] = (features[f'{prefix}_LOGIN_GROWTH'] < -1).astype(int)\n",
    "    \n",
    "    # 登录频率分级\n",
    "    features[f'{prefix}_LOGIN_LEVEL_HIGH'] = (features[f'{prefix}_LOGIN_1M'] >= 15).astype(int)\n",
    "    features[f'{prefix}_LOGIN_LEVEL_MID'] = ((features[f'{prefix}_LOGIN_1M'] >= 5) & (features[f'{prefix}_LOGIN_1M'] < 15)).astype(int)\n",
    "    features[f'{prefix}_LOGIN_LEVEL_LOW'] = ((features[f'{prefix}_LOGIN_1M'] > 0) & (features[f'{prefix}_LOGIN_1M'] < 5)).astype(int)\n",
    "    features[f'{prefix}_LOGIN_LEVEL_NONE'] = (features[f'{prefix}_LOGIN_1M'] == 0).astype(int)\n",
    "    \n",
    "    # 登录对数变换\n",
    "    features[f'{prefix}_LOGIN_1M_LOG'] = np.log1p(features[f'{prefix}_LOGIN_1M'])\n",
    "    features[f'{prefix}_LOGIN_3M_LOG'] = np.log1p(features[f'{prefix}_LOGIN_3M'])\n",
    "    \n",
    "    print(\"4. 活跃天数特征工程(MB_ACTV_CNT)\")\n",
    "    features[f'{prefix}_ACTV_1M'] = features['MB_ACTV_CNT_1M'].fillna(0)\n",
    "    features[f'{prefix}_ACTV_3M'] = features['MB_ACTV_CNT_3M'].fillna(0)\n",
    "    \n",
    "    # 活跃增长率和趋势\n",
    "    features[f'{prefix}_ACTV_AVG_3M'] = features[f'{prefix}_ACTV_3M'] / 3\n",
    "    features[f'{prefix}_ACTV_RATIO_1M_3M'] = features[f'{prefix}_ACTV_1M'] / (features[f'{prefix}_ACTV_AVG_3M'] + 1)\n",
    "    features[f'{prefix}_ACTV_GROWTH'] = features[f'{prefix}_ACTV_1M'] - features[f'{prefix}_ACTV_AVG_3M']\n",
    "    features[f'{prefix}_ACTV_IS_STABLE'] = (abs(features[f'{prefix}_ACTV_GROWTH']) < 1).astype(int)\n",
    "    features[f'{prefix}_ACTV_IS_INCREASING'] = (features[f'{prefix}_ACTV_GROWTH'] > 0).astype(int)\n",
    "    features[f'{prefix}_ACTV_IS_DECREASING'] = (features[f'{prefix}_ACTV_GROWTH'] < -1).astype(int)\n",
    "    \n",
    "    # 活跃频率分级\n",
    "    features[f'{prefix}_ACTV_LEVEL_HIGH'] = (features[f'{prefix}_ACTV_1M'] >= 15).astype(int)\n",
    "    features[f'{prefix}_ACTV_LEVEL_MID'] = ((features[f'{prefix}_ACTV_1M'] >= 5) & (features[f'{prefix}_ACTV_1M'] < 15)).astype(int)\n",
    "    features[f'{prefix}_ACTV_LEVEL_LOW'] = ((features[f'{prefix}_ACTV_1M'] > 0) & (features[f'{prefix}_ACTV_1M'] < 5)).astype(int)\n",
    "    features[f'{prefix}_ACTV_LEVEL_NONE'] = (features[f'{prefix}_ACTV_1M'] == 0).astype(int)\n",
    "    \n",
    "    # 活跃对数变换\n",
    "    features[f'{prefix}_ACTV_1M_LOG'] = np.log1p(features[f'{prefix}_ACTV_1M'])\n",
    "    features[f'{prefix}_ACTV_3M_LOG'] = np.log1p(features[f'{prefix}_ACTV_3M'])\n",
    "    \n",
    "    print(\"5. 浏览时长特征工程(VIEW_MINUTE)\")\n",
    "    features[f'{prefix}_VIEW_1M'] = features['VIEW_MINUTE_1M'].fillna(0)\n",
    "    features[f'{prefix}_VIEW_3M'] = features['VIEW_MINUTE_3M'].fillna(0)\n",
    "    \n",
    "    # 浏览时长增长率和趋势\n",
    "    features[f'{prefix}_VIEW_AVG_3M'] = features[f'{prefix}_VIEW_3M'] / 3\n",
    "    features[f'{prefix}_VIEW_RATIO_1M_3M'] = features[f'{prefix}_VIEW_1M'] / (features[f'{prefix}_VIEW_AVG_3M'] + 1)\n",
    "    features[f'{prefix}_VIEW_GROWTH'] = features[f'{prefix}_VIEW_1M'] - features[f'{prefix}_VIEW_AVG_3M']\n",
    "    features[f'{prefix}_VIEW_IS_STABLE'] = (abs(features[f'{prefix}_VIEW_GROWTH']) < 1).astype(int)\n",
    "    features[f'{prefix}_VIEW_IS_INCREASING'] = (features[f'{prefix}_VIEW_GROWTH'] > 0).astype(int)\n",
    "    features[f'{prefix}_VIEW_IS_DECREASING'] = (features[f'{prefix}_VIEW_GROWTH'] < -1).astype(int)\n",
    "    \n",
    "    # 浏览时长分级\n",
    "    features[f'{prefix}_VIEW_LEVEL_HIGH'] = (features[f'{prefix}_VIEW_1M'] >= 30).astype(int)\n",
    "    features[f'{prefix}_VIEW_LEVEL_MID'] = ((features[f'{prefix}_VIEW_1M'] >= 10) & (features[f'{prefix}_VIEW_1M'] < 30)).astype(int)\n",
    "    features[f'{prefix}_VIEW_LEVEL_LOW'] = ((features[f'{prefix}_VIEW_1M'] > 0) & (features[f'{prefix}_VIEW_1M'] < 10)).astype(int)\n",
    "    features[f'{prefix}_VIEW_LEVEL_NONE'] = (features[f'{prefix}_VIEW_1M'] == 0).astype(int)\n",
    "    \n",
    "    # 浏览时长对数变换\n",
    "    features[f'{prefix}_VIEW_1M_LOG'] = np.log1p(features[f'{prefix}_VIEW_1M'])\n",
    "    features[f'{prefix}_VIEW_3M_LOG'] = np.log1p(features[f'{prefix}_VIEW_3M'])\n",
    "    \n",
    "    print(\"6. 登录与活跃关系特征\")\n",
    "    features[f'{prefix}_LOGIN_ACTV_RATIO_1M'] = features[f'{prefix}_ACTV_1M'] / (features[f'{prefix}_LOGIN_1M'] + 1)\n",
    "    features[f'{prefix}_LOGIN_ACTV_RATIO_3M'] = features[f'{prefix}_ACTV_3M'] / (features[f'{prefix}_LOGIN_3M'] + 1)\n",
    "    features[f'{prefix}_LOGIN_ACTV_DIFF_1M'] = features[f'{prefix}_LOGIN_1M'] - features[f'{prefix}_ACTV_1M']\n",
    "    features[f'{prefix}_LOGIN_ACTV_DIFF_3M'] = features[f'{prefix}_LOGIN_3M'] - features[f'{prefix}_ACTV_3M']\n",
    "    features[f'{prefix}_IS_ALWAYS_ACTIVE'] = (features[f'{prefix}_LOGIN_ACTV_RATIO_1M'] >= 0.8).astype(int)\n",
    "    features[f'{prefix}_IS_RARELY_ACTIVE'] = ((features[f'{prefix}_LOGIN_1M'] > 0) & (features[f'{prefix}_ACTV_1M'] == 0)).astype(int)\n",
    "    \n",
    "    print(\"7. 浏览时长与活跃关系特征\")\n",
    "    features[f'{prefix}_VIEW_PER_ACTV_1M'] = features[f'{prefix}_VIEW_1M'] / (features[f'{prefix}_ACTV_1M'] + 1)\n",
    "    features[f'{prefix}_VIEW_PER_ACTV_3M'] = features[f'{prefix}_VIEW_3M'] / (features[f'{prefix}_ACTV_3M'] + 1)\n",
    "    features[f'{prefix}_VIEW_PER_LOGIN_1M'] = features[f'{prefix}_VIEW_1M'] / (features[f'{prefix}_LOGIN_1M'] + 1)\n",
    "    features[f'{prefix}_VIEW_PER_LOGIN_3M'] = features[f'{prefix}_VIEW_3M'] / (features[f'{prefix}_LOGIN_3M'] + 1)\n",
    "    features[f'{prefix}_IS_HIGH_VIEW_PER_ACTV'] = (features[f'{prefix}_VIEW_PER_ACTV_1M'] >= 10).astype(int)\n",
    "    features[f'{prefix}_IS_LOW_VIEW_PER_ACTV'] = ((features[f'{prefix}_ACTV_1M'] > 0) & (features[f'{prefix}_VIEW_PER_ACTV_1M'] < 2)).astype(int)\n",
    "    \n",
    "    print(\"8. 用户活跃度综合评分\")\n",
    "    features[f'{prefix}_ENGAGEMENT_SCORE_1M'] = (\n",
    "        features[f'{prefix}_LOGIN_1M'] * 0.3 +\n",
    "        features[f'{prefix}_ACTV_1M'] * 0.4 +\n",
    "        features[f'{prefix}_VIEW_1M'] * 0.3\n",
    "    )\n",
    "    features[f'{prefix}_ENGAGEMENT_SCORE_3M'] = (\n",
    "        features[f'{prefix}_LOGIN_3M'] * 0.3 +\n",
    "        features[f'{prefix}_ACTV_3M'] * 0.4 +\n",
    "        features[f'{prefix}_VIEW_3M'] * 0.3\n",
    "    )\n",
    "    features[f'{prefix}_ENGAGEMENT_SCORE_GROWTH'] = features[f'{prefix}_ENGAGEMENT_SCORE_1M'] - features[f'{prefix}_ENGAGEMENT_SCORE_3M'] / 3\n",
    "    \n",
    "    print(\"9. 用户活跃度等级\")\n",
    "    features[f'{prefix}_ENGAGEMENT_LEVEL_HIGH'] = (features[f'{prefix}_ENGAGEMENT_SCORE_1M'] >= 50).astype(int)\n",
    "    features[f'{prefix}_ENGAGEMENT_LEVEL_MID'] = ((features[f'{prefix}_ENGAGEMENT_SCORE_1M'] >= 20) & (features[f'{prefix}_ENGAGEMENT_SCORE_1M'] < 50)).astype(int)\n",
    "    features[f'{prefix}_ENGAGEMENT_LEVEL_LOW'] = ((features[f'{prefix}_ENGAGEMENT_SCORE_1M'] > 0) & (features[f'{prefix}_ENGAGEMENT_SCORE_1M'] < 20)).astype(int)\n",
    "    features[f'{prefix}_ENGAGEMENT_LEVEL_NONE'] = (features[f'{prefix}_ENGAGEMENT_SCORE_1M'] == 0).astype(int)\n",
    "    \n",
    "    print(\"10. 用户行为状态识别\")\n",
    "    features[f'{prefix}_IS_SILENT_USER'] = ((features[f'{prefix}_LOGIN_1M'] == 0) & (features[f'{prefix}_HAS_REG'] == 1)).astype(int)\n",
    "    features[f'{prefix}_IS_NEW_ACTIVE_USER'] = ((features[f'{prefix}_REG_TENURE_MONTHS'] <= 3) & (features[f'{prefix}_LOGIN_1M'] > 0)).astype(int)\n",
    "    features[f'{prefix}_IS_CHURNING_USER'] = ((features[f'{prefix}_LOGIN_3M'] > 0) & (features[f'{prefix}_LOGIN_1M'] == 0)).astype(int)\n",
    "    features[f'{prefix}_IS_SUPER_ACTIVE'] = ((features[f'{prefix}_LOGIN_1M'] >= 20) & (features[f'{prefix}_ACTV_1M'] >= 20)).astype(int)\n",
    "    \n",
    "    print(\"11. 注册时长与活跃度交叉特征\")\n",
    "    features[f'{prefix}_REG_LOGIN_CROSS'] = features[f'{prefix}_LOGIN_1M'] / (features[f'{prefix}_REG_TENURE_DAYS'] + 1)\n",
    "    features[f'{prefix}_REG_ACTV_CROSS'] = features[f'{prefix}_ACTV_1M'] / (features[f'{prefix}_REG_TENURE_DAYS'] + 1)\n",
    "    features[f'{prefix}_REG_VIEW_CROSS'] = features[f'{prefix}_VIEW_1M'] / (features[f'{prefix}_REG_TENURE_DAYS'] + 1)\n",
    "    features[f'{prefix}_REG_ENGAGEMENT_CROSS'] = features[f'{prefix}_ENGAGEMENT_SCORE_1M'] / (features[f'{prefix}_REG_TENURE_MONTHS'] + 1)\n",
    "    \n",
    "    print(\"12. 用户类型与活跃度交叉特征\")\n",
    "    features[f'{prefix}_TYPE_LOGIN_CROSS'] = features[f'{prefix}_CUST_TYPE_ENCODE'] * features[f'{prefix}_LOGIN_1M']\n",
    "    features[f'{prefix}_TYPE_ACTV_CROSS'] = features[f'{prefix}_CUST_TYPE_ENCODE'] * features[f'{prefix}_ACTV_1M']\n",
    "    features[f'{prefix}_TYPE_VIEW_CROSS'] = features[f'{prefix}_CUST_TYPE_ENCODE'] * features[f'{prefix}_VIEW_1M']\n",
    "    \n",
    "    # ========== 删除原始字段,只保留处理后的特征 ==========\n",
    "    print(\"\\n删除原始字段,只保留CUST_NO和处理后的特征...\")\n",
    "    original_cols = ['DATA_DAT', 'MB_REG_TIME', 'MB_CUST_TYPE']\n",
    "    \n",
    "    cols_to_drop = [col for col in original_cols if col in features.columns]\n",
    "    if cols_to_drop:\n",
    "        features = features.drop(columns=cols_to_drop)\n",
    "        print(f\"已删除原始字段: {cols_to_drop}\")\n",
    "    \n",
    "    print()\n",
    "    print(f\"处理后数据维度: {features.shape}\")\n",
    "    print(f\"新增特征数量: {features.shape[1] - 1}\")  # 减去CUST_NO\n",
    "    print()\n",
    "    \n",
    "    return features"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 37,
   "id": "1a61eed3",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "================================================================================\n",
      "处理掌银客户信息表 - 训练集\n",
      "原始数据维度: (83391, 10)\n",
      "================================================================================\n",
      "\n",
      "1. 掌银注册时间特征工程(MB_REG_TIME)\n",
      "2. 掌银客户类型特征工程(MB_CUST_TYPE)\n",
      "3. 登录天数特征工程(MB_LOGIN_CNT)\n",
      "4. 活跃天数特征工程(MB_ACTV_CNT)\n",
      "5. 浏览时长特征工程(VIEW_MINUTE)\n",
      "6. 登录与活跃关系特征\n",
      "7. 浏览时长与活跃关系特征\n",
      "8. 用户活跃度综合评分\n",
      "9. 用户活跃度等级\n",
      "10. 用户行为状态识别\n",
      "11. 注册时长与活跃度交叉特征\n",
      "12. 用户类型与活跃度交叉特征\n",
      "\n",
      "删除原始字段,只保留CUST_NO和处理后的特征...\n",
      "已删除原始字段: ['MB_REG_TIME', 'MB_CUST_TYPE']\n",
      "\n",
      "处理后数据维度: (83391, 113)\n",
      "新增特征数量: 112\n",
      "\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>DATA_DATE</th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>MB_LOGIN_CNT_1M</th>\n",
       "      <th>MB_LOGIN_CNT_3M</th>\n",
       "      <th>MB_ACTV_CNT_1M</th>\n",
       "      <th>MB_ACTV_CNT_3M</th>\n",
       "      <th>VIEW_MINUTE_1M</th>\n",
       "      <th>VIEW_MINUTE_3M</th>\n",
       "      <th>MB_HAS_REG</th>\n",
       "      <th>MB_REG_TENURE_DAYS</th>\n",
       "      <th>...</th>\n",
       "      <th>MB_IS_NEW_ACTIVE_USER</th>\n",
       "      <th>MB_IS_CHURNING_USER</th>\n",
       "      <th>MB_IS_SUPER_ACTIVE</th>\n",
       "      <th>MB_REG_LOGIN_CROSS</th>\n",
       "      <th>MB_REG_ACTV_CROSS</th>\n",
       "      <th>MB_REG_VIEW_CROSS</th>\n",
       "      <th>MB_REG_ENGAGEMENT_CROSS</th>\n",
       "      <th>MB_TYPE_LOGIN_CROSS</th>\n",
       "      <th>MB_TYPE_ACTV_CROSS</th>\n",
       "      <th>MB_TYPE_VIEW_CROSS</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>20131231</td>\n",
       "      <td>7fca4434fc640f6e8b0bdeaaab281a0f</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>4</td>\n",
       "      <td>0.00</td>\n",
       "      <td>7.85</td>\n",
       "      <td>1</td>\n",
       "      <td>1054</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>20131231</td>\n",
       "      <td>0bb5ff7f09659154c15db0022a20f825</td>\n",
       "      <td>3</td>\n",
       "      <td>15</td>\n",
       "      <td>3</td>\n",
       "      <td>15</td>\n",
       "      <td>4.03</td>\n",
       "      <td>15.39</td>\n",
       "      <td>1</td>\n",
       "      <td>879</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.003409</td>\n",
       "      <td>0.003409</td>\n",
       "      <td>0.00458</td>\n",
       "      <td>0.1103</td>\n",
       "      <td>3</td>\n",
       "      <td>3</td>\n",
       "      <td>4.03</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>20131231</td>\n",
       "      <td>8b264a90ce80c134db01fbd2310c973e</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>-0.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>20131231</td>\n",
       "      <td>0508f69d736ae662288c5337288914f2</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>-0.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>20131231</td>\n",
       "      <td>2d818f29bd0c43b76d5c7443216318b5</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>NaN</td>\n",
       "      <td>NaN</td>\n",
       "      <td>1</td>\n",
       "      <td>5033</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.00000</td>\n",
       "      <td>0.0000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 113 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "   DATA_DATE                           CUST_NO  MB_LOGIN_CNT_1M  \\\n",
       "0   20131231  7fca4434fc640f6e8b0bdeaaab281a0f                0   \n",
       "1   20131231  0bb5ff7f09659154c15db0022a20f825                3   \n",
       "2   20131231  8b264a90ce80c134db01fbd2310c973e                0   \n",
       "3   20131231  0508f69d736ae662288c5337288914f2                0   \n",
       "4   20131231  2d818f29bd0c43b76d5c7443216318b5                0   \n",
       "\n",
       "   MB_LOGIN_CNT_3M  MB_ACTV_CNT_1M  MB_ACTV_CNT_3M  VIEW_MINUTE_1M  \\\n",
       "0                1               0               4            0.00   \n",
       "1               15               3              15            4.03   \n",
       "2                0               0               0             NaN   \n",
       "3                0               0               0             NaN   \n",
       "4                0               0               0             NaN   \n",
       "\n",
       "   VIEW_MINUTE_3M  MB_HAS_REG  MB_REG_TENURE_DAYS  ...  MB_IS_NEW_ACTIVE_USER  \\\n",
       "0            7.85           1                1054  ...                      0   \n",
       "1           15.39           1                 879  ...                      0   \n",
       "2             NaN           0                   0  ...                      0   \n",
       "3             NaN           0                   0  ...                      0   \n",
       "4             NaN           1                5033  ...                      0   \n",
       "\n",
       "   MB_IS_CHURNING_USER  MB_IS_SUPER_ACTIVE  MB_REG_LOGIN_CROSS  \\\n",
       "0                    1                   0            0.000000   \n",
       "1                    0                   0            0.003409   \n",
       "2                    0                   0            0.000000   \n",
       "3                    0                   0            0.000000   \n",
       "4                    0                   0            0.000000   \n",
       "\n",
       "   MB_REG_ACTV_CROSS  MB_REG_VIEW_CROSS  MB_REG_ENGAGEMENT_CROSS  \\\n",
       "0           0.000000            0.00000                   0.0000   \n",
       "1           0.003409            0.00458                   0.1103   \n",
       "2           0.000000            0.00000                   0.0000   \n",
       "3           0.000000            0.00000                   0.0000   \n",
       "4           0.000000            0.00000                   0.0000   \n",
       "\n",
       "   MB_TYPE_LOGIN_CROSS  MB_TYPE_ACTV_CROSS  MB_TYPE_VIEW_CROSS  \n",
       "0                    0                   0                0.00  \n",
       "1                    3                   3                4.03  \n",
       "2                    0                   0               -0.00  \n",
       "3                    0                   0               -0.00  \n",
       "4                    0                   0                0.00  \n",
       "\n",
       "[5 rows x 113 columns]"
      ]
     },
     "execution_count": 37,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "TRAIN_MB_features = process_mb_cust_info_features(TRAIN_MB_CUST_INFO_data, is_train=True)\n",
    "TRAIN_MB_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f592a7dc",
   "metadata": {},
   "source": [
    "## 5. 特征合并与保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 40,
   "id": "599b1f38",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "开始合并训练集特征...\n",
      "\n",
      "训练集基础特征合并完成!\n",
      "合并后数据维度: (83391, 388)\n",
      "总特征数量: 387\n",
      "\n",
      "检查合并后缺失值...\n",
      "                 缺失数量  缺失比例(%)\n",
      "VIEW_MINUTE_1M  23912    28.67\n",
      "VIEW_MINUTE_3M  23912    28.67\n",
      "\n",
      "训练集基础特征合并完成!\n",
      "合并后数据维度: (83391, 388)\n",
      "总特征数量: 387\n",
      "\n",
      "检查合并后缺失值...\n",
      "                 缺失数量  缺失比例(%)\n",
      "VIEW_MINUTE_1M  23912    28.67\n",
      "VIEW_MINUTE_3M  23912    28.67\n"
     ]
    },
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>CUST_NO</th>\n",
       "      <th>NATURE_AGE</th>\n",
       "      <th>NATURE_AGE_SQUARE</th>\n",
       "      <th>NATURE_AGE_SQRT</th>\n",
       "      <th>NATURE_AGE_LOG</th>\n",
       "      <th>NATURE_AGE_GROUP_5</th>\n",
       "      <th>NATURE_AGE_GROUP_3</th>\n",
       "      <th>NATURE_IS_YOUNG</th>\n",
       "      <th>NATURE_IS_MIDDLE</th>\n",
       "      <th>NATURE_IS_OLD</th>\n",
       "      <th>...</th>\n",
       "      <th>MB_IS_NEW_ACTIVE_USER</th>\n",
       "      <th>MB_IS_CHURNING_USER</th>\n",
       "      <th>MB_IS_SUPER_ACTIVE</th>\n",
       "      <th>MB_REG_LOGIN_CROSS</th>\n",
       "      <th>MB_REG_ACTV_CROSS</th>\n",
       "      <th>MB_REG_VIEW_CROSS</th>\n",
       "      <th>MB_REG_ENGAGEMENT_CROSS</th>\n",
       "      <th>MB_TYPE_LOGIN_CROSS</th>\n",
       "      <th>MB_TYPE_ACTV_CROSS</th>\n",
       "      <th>MB_TYPE_VIEW_CROSS</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>0</th>\n",
       "      <td>ee39a885c34dc7b3ef1d6078368608c4</td>\n",
       "      <td>38</td>\n",
       "      <td>1444</td>\n",
       "      <td>6.164414</td>\n",
       "      <td>3.663562</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.004323</td>\n",
       "      <td>0.005764</td>\n",
       "      <td>0.009409</td>\n",
       "      <td>0.185792</td>\n",
       "      <td>3</td>\n",
       "      <td>4</td>\n",
       "      <td>6.53</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>1</th>\n",
       "      <td>8af0ca419efe285afe630122f1bc668c</td>\n",
       "      <td>43</td>\n",
       "      <td>1849</td>\n",
       "      <td>6.557439</td>\n",
       "      <td>3.784190</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000199</td>\n",
       "      <td>0.000199</td>\n",
       "      <td>0.000497</td>\n",
       "      <td>0.008613</td>\n",
       "      <td>1</td>\n",
       "      <td>1</td>\n",
       "      <td>2.49</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>2</th>\n",
       "      <td>4f5882570aa441c90fbecef4a1cba1a3</td>\n",
       "      <td>47</td>\n",
       "      <td>2209</td>\n",
       "      <td>6.855655</td>\n",
       "      <td>3.871201</td>\n",
       "      <td>4</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000776</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.009302</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>3</th>\n",
       "      <td>11e1109832dfab1ee347acf2f8f12a30</td>\n",
       "      <td>32</td>\n",
       "      <td>1024</td>\n",
       "      <td>5.656854</td>\n",
       "      <td>3.496508</td>\n",
       "      <td>2</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.00</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>4</th>\n",
       "      <td>6efb73cba2d64ac1c4b1cc0541b9f8b8</td>\n",
       "      <td>44</td>\n",
       "      <td>1936</td>\n",
       "      <td>6.633250</td>\n",
       "      <td>3.806662</td>\n",
       "      <td>3</td>\n",
       "      <td>2</td>\n",
       "      <td>0</td>\n",
       "      <td>1</td>\n",
       "      <td>0</td>\n",
       "      <td>...</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0.000000</td>\n",
       "      <td>0</td>\n",
       "      <td>0</td>\n",
       "      <td>-0.00</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "<p>5 rows × 388 columns</p>\n",
       "</div>"
      ],
      "text/plain": [
       "                            CUST_NO  NATURE_AGE  NATURE_AGE_SQUARE  \\\n",
       "0  ee39a885c34dc7b3ef1d6078368608c4          38               1444   \n",
       "1  8af0ca419efe285afe630122f1bc668c          43               1849   \n",
       "2  4f5882570aa441c90fbecef4a1cba1a3          47               2209   \n",
       "3  11e1109832dfab1ee347acf2f8f12a30          32               1024   \n",
       "4  6efb73cba2d64ac1c4b1cc0541b9f8b8          44               1936   \n",
       "\n",
       "   NATURE_AGE_SQRT  NATURE_AGE_LOG  NATURE_AGE_GROUP_5  NATURE_AGE_GROUP_3  \\\n",
       "0         6.164414        3.663562                   3                   2   \n",
       "1         6.557439        3.784190                   3                   2   \n",
       "2         6.855655        3.871201                   4                   2   \n",
       "3         5.656854        3.496508                   2                   2   \n",
       "4         6.633250        3.806662                   3                   2   \n",
       "\n",
       "   NATURE_IS_YOUNG  NATURE_IS_MIDDLE  NATURE_IS_OLD  ...  \\\n",
       "0                0                 1              0  ...   \n",
       "1                0                 1              0  ...   \n",
       "2                0                 1              0  ...   \n",
       "3                0                 1              0  ...   \n",
       "4                0                 1              0  ...   \n",
       "\n",
       "   MB_IS_NEW_ACTIVE_USER  MB_IS_CHURNING_USER  MB_IS_SUPER_ACTIVE  \\\n",
       "0                      0                    0                   0   \n",
       "1                      0                    0                   0   \n",
       "2                      0                    1                   0   \n",
       "3                      0                    1                   0   \n",
       "4                      0                    0                   0   \n",
       "\n",
       "   MB_REG_LOGIN_CROSS  MB_REG_ACTV_CROSS  MB_REG_VIEW_CROSS  \\\n",
       "0            0.004323           0.005764           0.009409   \n",
       "1            0.000199           0.000199           0.000497   \n",
       "2            0.000000           0.000776           0.000000   \n",
       "3            0.000000           0.000000           0.000000   \n",
       "4            0.000000           0.000000           0.000000   \n",
       "\n",
       "   MB_REG_ENGAGEMENT_CROSS  MB_TYPE_LOGIN_CROSS  MB_TYPE_ACTV_CROSS  \\\n",
       "0                 0.185792                    3                   4   \n",
       "1                 0.008613                    1                   1   \n",
       "2                 0.009302                    0                   0   \n",
       "3                 0.000000                    0                   0   \n",
       "4                 0.000000                    0                   0   \n",
       "\n",
       "   MB_TYPE_VIEW_CROSS  \n",
       "0                6.53  \n",
       "1                2.49  \n",
       "2                0.00  \n",
       "3                0.00  \n",
       "4               -0.00  \n",
       "\n",
       "[5 rows x 388 columns]"
      ]
     },
     "execution_count": 40,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "print(\"\\n开始合并训练集特征...\")\n",
    "TRAIN_base_features = TRAIN_NATURE_features.merge(TRAIN_ASSET_features, on='CUST_NO', how='left')\n",
    "TRAIN_base_features = TRAIN_base_features.merge(TRAIN_PROD_features, on='CUST_NO', how='left')\n",
    "TRAIN_base_features = TRAIN_base_features.merge(TRAIN_MB_features, on='CUST_NO', how='left')\n",
    "\n",
    "print(f\"\\n训练集基础特征合并完成!\")\n",
    "print(f\"合并后数据维度: {TRAIN_base_features.shape}\")\n",
    "print(f\"总特征数量: {TRAIN_base_features.shape[1] - 1}\")\n",
    "\n",
    "print(\"\\n检查合并后缺失值...\")\n",
    "missing = TRAIN_base_features.isnull().sum()\n",
    "missing_pct = (missing / len(TRAIN_base_features) * 100).round(2)\n",
    "missing_df = pd.DataFrame({\n",
    "    '缺失数量': missing[missing > 0],\n",
    "    '缺失比例(%)': missing_pct[missing > 0]\n",
    "}).sort_values('缺失比例(%)', ascending=False)\n",
    "if len(missing_df) > 0:\n",
    "    print(missing_df.head(20))\n",
    "else:\n",
    "    print(\"无缺失值\")\n",
    "\n",
    "TRAIN_base_features.head()"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "f2e59426",
   "metadata": {},
   "source": [
    "### 训练集保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 42,
   "id": "cafaffa6",
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "\n",
      "保存训练集特征...\n",
      "  - TRAIN_BASE_features.pkl 已保存, 维度: (83391, 388)\n"
     ]
    }
   ],
   "source": [
    "# 创建feature目录\n",
    "feature_dir = './feature/Train'\n",
    "if not os.path.exists(feature_dir):\n",
    "    os.makedirs(feature_dir)\n",
    "    print(f\"创建特征目录: {feature_dir}\")\n",
    "\n",
    "# 保存训练集特征\n",
    "print(\"\\n保存训练集特征...\")\n",
    "with open(os.path.join(feature_dir, 'TRAIN_BASE_features.pkl'), 'wb') as f:\n",
    "    pickle.dump(TRAIN_base_features, f)\n",
    "print(f\"  - TRAIN_BASE_features.pkl 已保存, 维度: {TRAIN_base_features.shape}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "id": "985fdd11",
   "metadata": {},
   "source": [
    "### 测试集保存"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79002939",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "markdown",
   "id": "f00bcd35",
   "metadata": {},
   "source": [
    "## 6. 测试集特征工程(预留)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "a0557824",
   "metadata": {},
   "outputs": [],
   "source": [
    "# A_NATURE_features = process_nature_features(A_NATURE_data, is_train=False)\n",
    "# A_ASSET_features = process_asset_features(A_ASSET_DEBT_data, is_train=False)\n",
    "# A_PROD_features = process_prod_hold_features(A_PROD_HOLD_data, is_train=False)\n",
    "# A_MB_features = process_mb_cust_info_features(A_MB_CUST_INFO_data, is_train=False)\n",
    "\n",
    "# print(\"\\n开始合并测试集特征...\")\n",
    "# A_base_features = A_NATURE_features.merge(A_ASSET_features, on='CUST_NO', how='left')\n",
    "# A_base_features = A_base_features.merge(A_PROD_features, on='CUST_NO', how='left')\n",
    "# A_base_features = A_base_features.merge(A_MB_features, on='CUST_NO', how='left')\n",
    "\n",
    "# print(f\"\\n测试集基础特征合并完成!\")\n",
    "# print(f\"合并后数据维度: {A_base_features.shape}\")\n",
    "\n",
    "# print(\"\\n保存测试集特征...\")\n",
    "# with open('./feature/A_base_features.pkl', 'wb') as f:\n",
    "#     pickle.dump(A_base_features, f)\n",
    "# print(\"测试集基础特征已保存至: ./feature/A_base_features.pkl\")\n",
    "\n",
    "# with open('./feature/A_NATURE_features.pkl', 'wb') as f:\n",
    "#     pickle.dump(A_NATURE_features, f)\n",
    "# with open('./feature/A_ASSET_features.pkl', 'wb') as f:\n",
    "#     pickle.dump(A_ASSET_features, f)\n",
    "# with open('./feature/A_PROD_features.pkl', 'wb') as f:\n",
    "#     pickle.dump(A_PROD_features, f)\n",
    "# with open('./feature/A_MB_features.pkl', 'wb') as f:\n",
    "#     pickle.dump(A_MB_features, f)\n",
    "\n",
    "# print(\"\\n测试集所有特征保存完成!\")"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "starcup",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.18"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
